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 < | ||
+ | %appdata%\Adobe\Adobe Photoshop 2022\Adobe Photoshop 2022 Settings\PSUserConfig | ||
+ | at path: | ||
+ | %appdata%\Adobe\Adobe Photoshop 2022\Adobe Photoshop 2022 Settings\ | ||
+ | </ | ||
+ | * with content inside PSUserConfig.txt and also PSUserConfig (no ext in case for win10) (also enable ps use wacom instead ink code here)< | ||
+ | UseSystemStylus 0 | ||
+ | </ | ||
+ | * more config text cmd here: https:// | ||
+ | ====== 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 || 0; // default not as list but as dict | ||
+ | } | ||
+ | </ | ||
+ | * ref: https:// | ||
+ | * startswith support | ||
+ | * check JS version: $.about() | ||
+ | |||
+ | * ref: | ||
+ | * https:// | ||
+ | * https:// | ||
+ | |||
+ | ====== 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 Scripting and Automation ====== | ||
+ | * Photoshop script guide can be found in application directory like < | ||
+ | C:\Program Files (x86)\Adobe\Adobe Photoshop CS3\Scripting Guide\</ | ||
+ | |||
+ | * 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> | ||
+ | psApp=win32com.client.Dispatch(' | ||
+ | psApp.preferences.rulerUnits=1 # use pixel as unit | ||
+ | psApp.documents.add() | ||
+ | </ | ||
+ | * python: with comtypes module, it can launch Photoshop with command shell (not working for me)<code python> | ||
+ | from comtypes.client import CreateObject | ||
+ | psApp=CreateObject(' | ||
+ | </ | ||
+ | * a ref video on Photoshop CS3 Python Interpreter Embed in C++ | ||
+ | * http:// | ||
+ | * another C++ listener integration | ||
+ | * http:// | ||
+ | * https:// | ||
+ | |||
+ | * What is new about scripting in Photoshop CS5 | ||
+ | * Guides where added to you can create/ | ||
+ | * 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:// | ||
+ | * get jsh.jsx for javascript script panel inside photoshop and other js conversion tool stuff | ||
+ | * [[http:// | ||
+ | * http:// | ||
+ | * https:// | ||
+ | * Export layer or group with trim: https:// | ||
+ | * a good website by a Japanese guy: http:// | ||
+ | * sorting layer: http:// | ||
+ | * tons of js code: http:// | ||
+ | * http:// | ||
+ | * http:// | ||
+ | * http:// | ||
+ | * http:// | ||
+ | |||
+ | * alert(" | ||
+ | |||
+ | * add guide line (before cs5) <code javascript> | ||
+ | // add guide; ref: http:// | ||
+ | function addGuide(offset, | ||
+ | var id1 = charIDToTypeID(' | ||
+ | var desc1 = new ActionDescriptor(); | ||
+ | var id2 = charIDToTypeID(' | ||
+ | var desc2 = new ActionDescriptor(); | ||
+ | var id3 = charIDToTypeID(' | ||
+ | var id4 = charIDToTypeID('# | ||
+ | desc2.putUnitDouble(id3, | ||
+ | var id5 = charIDToTypeID(' | ||
+ | var id6 = charIDToTypeID(' | ||
+ | var id7 = charIDToTypeID(orientation); | ||
+ | desc2.putEnumerated(id5, | ||
+ | var id8 = charIDToTypeID(' | ||
+ | desc1.putObject(id2, | ||
+ | executeAction(id1, | ||
+ | } | ||
+ | </ | ||
+ | * after CS5 <code javascript> | ||
+ | activeDocument.guides.removeAll() | ||
+ | </ | ||
+ | |||
+ | |||
+ | ===== system code ===== | ||
+ | |||
+ | * photoshop version and preference< | ||
+ | app.preferences.rulerUnits = Units.PIXELS | ||
+ | app.preferences.typeUnits = TypeUnits.PIXELS | ||
+ | app.displayDialogs = DialogModes.NO | ||
+ | </ | ||
+ | * default check if doc open for those process script <code javascript> | ||
+ | // ensure at least one document open | ||
+ | if (!documents.length) { | ||
+ | alert(' | ||
+ | defaultRunForNoDoc(); | ||
+ | } | ||
+ | else { | ||
+ | main(); // if at least one document exists, then proceed | ||
+ | } | ||
+ | </ | ||
+ | * ask user input <code javascript> | ||
+ | * confirm user <code javascript> | ||
+ | * safe way to call a function <code javascript> | ||
+ | // begin try/catch | ||
+ | try { | ||
+ | | ||
+ | } | ||
+ | // display error message if something goes wrong | ||
+ | catch(e) { | ||
+ | if (confirm(' | ||
+ | alert(e + ': on line ' + e.line, ' | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | * select tool< | ||
+ | function selectTool(tool) { | ||
+ | var my_desc = new ActionDescriptor(); | ||
+ | var my_ref = new ActionReference(); | ||
+ | my_ref.putClass( app.stringIDToTypeID(tool) ); | ||
+ | my_desc.putReference( app.charIDToTypeID(' | ||
+ | executeAction( app.charIDToTypeID(' | ||
+ | }; | ||
+ | selectTool(' | ||
+ | /** | ||
+ | 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 | ||
+ | */ | ||
+ | </ | ||
+ | ===== 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( " | ||
+ | var my_desc = new ActionDescriptor(); | ||
+ | my_desc.putReference( c2t( " | ||
+ | executeAction( c2t(" | ||
+ | }; | ||
+ | |||
+ | function brushSize(brushSize) { | ||
+ | var my_ref = new ActionReference(); | ||
+ | my_ref.putEnumerated( c2t(" | ||
+ | var my_descBrush = new ActionDescriptor(); | ||
+ | my_descBrush.putUnitDouble( s2t(" | ||
+ | | ||
+ | var my_desc = new ActionDescriptor(); | ||
+ | my_desc.putReference( c2t(" | ||
+ | my_desc.putObject( c2t(" | ||
+ | executeAction( c2t(" | ||
+ | }; | ||
+ | |||
+ | function brushHard(hardness) { | ||
+ | if(parseInt(hardness) > 100) hardness= 100; | ||
+ | var my_ref = new ActionReference(); | ||
+ | my_ref.putEnumerated( c2t(" | ||
+ | var my_descBrush = new ActionDescriptor(); | ||
+ | my_descBrush.putUnitDouble( s2t(" | ||
+ | | ||
+ | var my_desc = new ActionDescriptor(); | ||
+ | my_desc.putReference( c2t(" | ||
+ | my_desc.putObject( c2t(" | ||
+ | executeAction( c2t(" | ||
+ | }; | ||
+ | |||
+ | selectBrush(" | ||
+ | brushSize(20); | ||
+ | brushHard(20); | ||
+ | </ | ||
+ | |||
+ | ===== Layer operation code ===== | ||
+ | * get layer count <code javascript> | ||
+ | * new/delete layer <code javascript> | ||
+ | activeDocument.activeLayer.remove(); | ||
+ | |||
+ | activeDocument.activeLayer.kind = LayerKind.TEXT; | ||
+ | var textItem = activeDocument.activeLayer.textItem; | ||
+ | textItem.contents = "My Layer is " | ||
+ | textItem.size = 24; | ||
+ | textItem.font = " | ||
+ | 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 | ||
+ | </ | ||
+ | * rename layer <code javascript> | ||
+ | * select layer by name <code javascript> | ||
+ | // inside a set | ||
+ | activeDocument.activeLayer = activeDocument.layerSets[" | ||
+ | </ | ||
+ | * layer opacity <code javascript> | ||
+ | * layer blend mode <code javascript> | ||
+ | * toggle layer lock <code javascript> | ||
+ | * toggle layer visibility <code javascript> | ||
+ | * clip mask layer for below <code javascript> | ||
+ | * check bg layer mode <code javascript> | ||
+ | * select up, | ||
+ | function selectLayerNav(direction){ | ||
+ | var my_ref = new ActionReference(); | ||
+ | var my_desc = new ActionDescriptor(); | ||
+ | my_ref.putEnumerated( charIDToTypeID(" | ||
+ | my_desc.putReference( charIDToTypeID(" | ||
+ | my_desc.putBoolean( charIDToTypeID( " | ||
+ | executeAction( charIDToTypeID(" | ||
+ | } | ||
+ | selectLayerNav(" | ||
+ | </ | ||
+ | * rasterize layer <code javascript> | ||
+ | if (activeDocument.activeLayer.kind == LayerKind.NORMAL) | ||
+ | {activeDocument.activeLayer.rasterize(RasterizeType.ENTIRELAYER); | ||
+ | if (activeDocument.activeLayer.kind == LayerKind.TEXT) | ||
+ | {activeDocument.activeLayer.rasterize(RasterizeType.TEXTCONTENTS); | ||
+ | if (activeDocument.activeLayer.kind == LayerKind.SOLIDFILL) | ||
+ | {activeDocument.activeLayer.rasterize(RasterizeType.SHAPE); | ||
+ | </ | ||
+ | |||
+ | * 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(" | ||
+ | </ | ||
+ | ===== Doc operation code ===== | ||
+ | |||
+ | * new doc file and close <code javascript> | ||
+ | // < | ||
+ | |||
+ | var defaultRulerUnits = preferences.rulerUnits; | ||
+ | preferences.rulerUnits = Units.PIXELS; | ||
+ | var newDocumentRef = documents.add(800, | ||
+ | // 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(); | ||
+ | } | ||
+ | </ | ||
+ | * resize doc <code javascript> | ||
+ | * 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, | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | * file and folder <code javascript> | ||
+ | var samplesFolder = Folder(app.path + "/ | ||
+ | var fileList = samplesFolder.getFiles() | ||
+ | </ | ||
+ | * get active doc <code javascript> | ||
+ | var docName = app.activeDocument.name; | ||
+ | fileFullPath = app.activeDocument.fullName; | ||
+ | fileFolderPath = app.activeDocument.path; | ||
+ | |||
+ | index = (docName.name).indexOf(" | ||
+ | fileName = (docName.name).substr(0, | ||
+ | </ | ||
+ | |||
+ | * publishJPG.jsx : save current PSD file as fileName_publish.jpg in same folder <code javascript publishJPG.jsx> | ||
+ | //ref : http:// | ||
+ | function saveJPEG( doc, saveFile, qty ) { | ||
+ | var saveOptions = new JPEGSaveOptions( ); | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | } | ||
+ | //saveJPEG( app.activeDocument, | ||
+ | |||
+ | var doc = app.activeDocument; | ||
+ | var docName = doc.name; | ||
+ | docName = docName.match(/ | ||
+ | var suffix = ' | ||
+ | var savedName = decodeURI(doc.path)+'/' | ||
+ | if (! File(savedName).exists ){ | ||
+ | var savedFile = new File(decodeURI(doc.path)+'/' | ||
+ | | ||
+ | } | ||
+ | </ | ||
+ | * 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; | ||
+ | if(!doc.layers[i].isBackgroundLayer){ | ||
+ | layers.push(doc.layers[i]); | ||
+ | } | ||
+ | } | ||
+ | // Hide all the layers | ||
+ | for (var i = 0; i < layers.length; | ||
+ | layers[i].visible = false; | ||
+ | } | ||
+ | |||
+ | // Loop through each layer and print it | ||
+ | for (var i = 0; i < layers.length; | ||
+ | // Show the current layer | ||
+ | layers[i].visible = true; | ||
+ | | ||
+ | // Print the current layer | ||
+ | app.activeDocument.print(); | ||
+ | // | ||
+ | | ||
+ | // Hide the current layer | ||
+ | layers[i].visible = false; | ||
+ | } | ||
+ | |||
+ | // Show all the layers again | ||
+ | for (var i = 0; i < layers.length; | ||
+ | layers[i].visible = true; | ||
+ | } | ||
+ | } else { | ||
+ | alert(" | ||
+ | } | ||
+ | </ | ||
+ | * 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(" | ||
+ | |||
+ | // 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, | ||
+ | |||
+ | // 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, | ||
+ | } | ||
+ | // | ||
+ | //# trim border | ||
+ | // | ||
+ | app.activeDocument = newDoc; | ||
+ | newDoc.trim(TrimType.TOPLEFT, | ||
+ | </ | ||
+ | ===== Execute code ===== | ||
+ | |||
+ | * open folder <code javascript> | ||
+ | var f = app.activeDocument.path; | ||
+ | //var f = Folder(Folder.desktop+'/ | ||
+ | f.execute(); | ||
+ | * run a cmd script <code javascript> | ||
+ | file.execute(); | ||
+ | </ | ||
+ | * run js file, method 1 <code javascript>// | ||
+ | // above line is including other file, you need to put // in front.</ | ||
+ | * 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 " | ||
+ | alert(result); | ||
+ | </ | ||
+ | alert(' | ||
+ | result = " | ||
+ | </ | ||
+ | * example: https:// | ||
+ | * 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 = " | ||
+ | //var jsx_file = " | ||
+ | //var jsx_file = "/ | ||
+ | mydesc = new ActionDescriptor(); | ||
+ | mydesc.putPath(charIDToTypeID(" | ||
+ | mydesc.putString(charIDToTypeID(" | ||
+ | executeAction( stringIDToTypeID( " | ||
+ | </ | ||
+ | * run py file <code javascript> | ||
+ | var myPy = new File("/ | ||
+ | myPy.execute(); | ||
+ | </ | ||
+ | |||
+ | ===== Memory and Data store and retrieve ===== | ||
+ | |||
+ | * ref: http:// | ||
+ | * example code with keystrock detect " | ||
+ | |||
+ | ===== Javascript GUI scripting ===== | ||
+ | |||
+ | * read Photoshop sample script code like this: | ||
+ | * http:// | ||
+ | * http:// | ||
+ | * ScriptUI GUI code generator (broken link): http:// | ||
+ | * since that link is outdated, you can grab from http:// | ||
+ | * or download from my save "CS UI Buider v1.0.2 by Jakub Kozniewski" | ||
+ | * note: the 2.0a is a exe version, which not work for me, you can get from here https:// | ||
+ | * {{: | ||
+ | * There is also AfterEffect jsx script do ScriptUI design called P9 ScriptUI builder: https:// | ||
+ | |||
+ | * 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 = " | ||
+ | text: 'My Window Title', | ||
+ | bounds: [100, | ||
+ | w_label: StaticText { text:' | ||
+ | w_input: EditText { text:' | ||
+ | h_label: StaticText { text:' | ||
+ | h_input: EditText { text:' | ||
+ | my_btn: Button { text:' | ||
+ | run_btn: Button { text:' | ||
+ | quit_btn: Button { text:' | ||
+ | }"; | ||
+ | var ui = new Window(ui_code); | ||
+ | ui.my_btn.onClick = function() {alert(" | ||
+ | var result = ui.show(); | ||
+ | alert(result + ' | ||
+ | } | ||
+ | winRun(); | ||
+ | </ | ||
+ | |||
+ | |||
+ | ===== ScriptUI in Detail ===== | ||
+ | |||
+ | **Window Type - Pallete and Dialog** | ||
+ | * There are 2 window type | ||
+ | - pallete (non-modal window, not block user and Application interaction, | ||
+ | * Note: Photoshop, Illustrator will not keep display it; AfterEffects will keep display it | ||
+ | - dialog (modal window, block user and Application interaction, | ||
+ | * 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 = " | ||
+ | var win_type = " | ||
+ | var ui = win_type+" | ||
+ | var slider_panel = " | ||
+ | slider_panel +=" | ||
+ | slider_panel +=" | ||
+ | slider_panel +=" | ||
+ | slider_panel +=" | ||
+ | ui+=slider_panel; | ||
+ | var btm_grp = " | ||
+ | btm_grp +=" | ||
+ | btm_grp +=" | ||
+ | btm_grp +=" | ||
+ | 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(); | ||
+ | </ | ||
+ | |||
+ | **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:// | ||
+ | * http:// | ||
+ | * sample code, change target to your application, | ||
+ | function WinObject() { | ||
+ | var win_type = " | ||
+ | var ui = win_type+" | ||
+ | var slider_panel = " | ||
+ | slider_panel +=" | ||
+ | slider_panel +=" | ||
+ | slider_panel +=" | ||
+ | slider_panel +=" | ||
+ | ui+=slider_panel; | ||
+ | var btm_grp = " | ||
+ | btm_grp +=" | ||
+ | btm_grp +=" | ||
+ | btm_grp +=" | ||
+ | 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 = " | ||
+ | bt.target = " | ||
+ | //bt.target = " | ||
+ | bt.body = message; | ||
+ | bt.send(); | ||
+ | </ | ||
+ | * 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 = " | ||
+ | // The script to be executed as a String | ||
+ | var message = " | ||
+ | // assign to the object' | ||
+ | bt.body = message; | ||
+ | // send the message to the target app | ||
+ | bt.send(); // an alert will pop-up in Photoshop | ||
+ | </ | ||
+ | |||
+ | **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=" | ||
+ | * 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 = " | ||
+ | var currentPath = (new File($.fileName)).path; | ||
+ | var scriptToLoad = new File (currentPath + "/" | ||
+ | try { | ||
+ | if (!scriptToLoad.exists) { throw new Error(" | ||
+ | scriptToLoad.open (" | ||
+ | var message = scriptToLoad.read(); | ||
+ | scriptToLoad.close(); | ||
+ | } catch (error) { | ||
+ | alert(" | ||
+ | } | ||
+ | var bt = new BridgeTalk(); | ||
+ | bt.target = " | ||
+ | bt.body = message; | ||
+ | bt.send(); | ||
+ | </ | ||
+ | |||
+ | **Special Note for BridgeTalk Method** | ||
+ | * use " | ||
+ | * 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 = " | ||
+ | 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 = " | ||
+ | bt.body = message; | ||
+ | bt.send(); | ||
+ | </ | ||
+ | |||
+ | ===== 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:// | ||
+ | * a jsxbin decoder project in c#: https:// | ||
+ | |||
+ | ====== 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 == ' | ||
+ | from appscript import * | ||
+ | psApp = app(' | ||
+ | else: | ||
+ | from win32com.client import Dispatch | ||
+ | psApp = Dispatch(' | ||
+ | #import comtypes | ||
+ | #psApp = comtypes.client.CreateObject(' | ||
+ | except: | ||
+ | pass | ||
+ | </ | ||
+ | |||
+ | * extra reading for related: | ||
+ | * com way to automate keystroke for app automation in python (of course, win only): | ||
+ | * http:// | ||
+ | * com way to python and 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:// | ||
+ | |||
+ | * **method 1: use Com to Ask Photoshop to run a JSX file, Python + COM + javascript** | ||
+ | * http:// | ||
+ | * js function creation by built-in photoshop listener: http:// | ||
+ | * python code <code python callJS.py> | ||
+ | def runJSX (aFilePath ): | ||
+ | id60 = psApp.stringIDToTypeID ( " | ||
+ | desc12 = comtypes.client.CreateObject(' | ||
+ | id61 = psApp.charIDToTypeID( " | ||
+ | desc12.putPath( id61, aFilePath ) | ||
+ | id62 = psApp.charIDToTypeID( " | ||
+ | desc12.putString( id62, " | ||
+ | psApp.executeAction( id60, desc12, 2 ) | ||
+ | |||
+ | psApp = comtypes.client.CreateObject(' | ||
+ | runJSX ( " | ||
+ | </ | ||
+ | * additional support, **call python from javascript** <code javascript callPy.jsx> | ||
+ | var myPy = new File("/ | ||
+ | myPy.execute(); | ||
+ | </ | ||
+ | * **method 2: Python(Qt) + comtypes/ | ||
+ | * http:// | ||
+ | * http:// | ||
+ | * https:// | ||
+ | * http:// | ||
+ | * python code <code python> | ||
+ | import sys | ||
+ | from PyQt4.QtGui import * | ||
+ | import comtypes.client | ||
+ | |||
+ | class LayerSetsExporter(QWidget): | ||
+ | |||
+ | def __init__(self, | ||
+ | super(LayerSetsExporter, | ||
+ | self.createLayout() | ||
+ | self.createConnection() | ||
+ | |||
+ | def setLayerSetsVisible(self, | ||
+ | for layerSet in layerSets: | ||
+ | layerSet.visible = False | ||
+ | |||
+ | def processRun(self): | ||
+ | outputPath = ' | ||
+ | |||
+ | app = comtypes.client.CreateObject(' | ||
+ | doc = app.activeDocument | ||
+ | |||
+ | optionJpg = comtypes.client.CreateObject(' | ||
+ | optionJpg.quality = 8 | ||
+ | |||
+ | count = 0 | ||
+ | layerSets = doc.layerSets | ||
+ | for layerSet in layerSets: | ||
+ | self.setLayerSetsVisible(layerSets, | ||
+ | layerSet.visible = True | ||
+ | doc.saveAs(outputPath + str(count) + ' | ||
+ | count += 1 | ||
+ | |||
+ | def createLayout(self): | ||
+ | self.btnRun = QPushButton('& | ||
+ | layout = QVBoxLayout() | ||
+ | layout.addWidget(self.btnRun) | ||
+ | |||
+ | self.resize(200, | ||
+ | self.setWindowTitle(' | ||
+ | self.setLayout(layout) | ||
+ | |||
+ | def createConnection(self): | ||
+ | self.btnRun.clicked.connect(self.processRun) | ||
+ | |||
+ | qApp = QApplication(sys.argv) | ||
+ | |||
+ | exporter = LayerSetsExporter() | ||
+ | exporter.show() | ||
+ | |||
+ | qApp.exec_() | ||
+ | </ | ||
+ | * additional support, **convert Photoshop Listener output javascript code to python code** | ||
+ | * http:// | ||
+ | * python code <code python> | ||
+ | def hueSaturation(hue, | ||
+ | if psApp is None: | ||
+ | psApp = win32.gencache.EnsureDispatch(' | ||
+ | dialogMode = 3 | ||
+ | idHStr = psApp.CharIDToTypeID( " | ||
+ | desc169 = win32.gencache.EnsureDispatch( " | ||
+ | idpresetKind = psApp.StringIDToTypeID( " | ||
+ | idpresetKindType = psApp.StringIDToTypeID( " | ||
+ | idpresetKindCustom = psApp.StringIDToTypeID( " | ||
+ | desc169.PutEnumerated( idpresetKind, | ||
+ | idClrz = psApp.CharIDToTypeID( " | ||
+ | desc169.PutBoolean( idClrz, colorize ) | ||
+ | idAdjs = psApp.CharIDToTypeID( " | ||
+ | list27 = win32.gencache.EnsureDispatch( " | ||
+ | desc170 = win32.gencache.EnsureDispatch( " | ||
+ | idH = psApp.CharIDToTypeID( " | ||
+ | desc170.PutInteger( idH, hue ) | ||
+ | idStrt = psApp.CharIDToTypeID( " | ||
+ | desc170.PutInteger( idStrt, saturation ) | ||
+ | idLght = psApp.CharIDToTypeID( " | ||
+ | desc170.PutInteger( idLght, lightness ) | ||
+ | idHsttwo = psApp.CharIDToTypeID( " | ||
+ | list27.PutObject( idHsttwo, desc170 ) | ||
+ | desc169.PutList( idAdjs, list27 ) | ||
+ | psApp.ExecuteAction( idHStr, desc169, dialogMode ) | ||
+ | </ | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ====== 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:// | ||
+ | * ref - more 2 way solution: http:// | ||
+ | * 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)) | ||
+ | { | ||
+ | // 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 + " | ||
+ | } | ||
+ | else { | ||
+ | incoming.writeln(" | ||
+ | } | ||
+ | } | ||
+ | catch (err) { | ||
+ | incoming.writeln(err + " | ||
+ | incoming.close(); | ||
+ | delete incoming; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | * call from python next <code python ps_connect_caller.py> | ||
+ | import socket | ||
+ | HOST = ' | ||
+ | PORT = 8123 | ||
+ | |||
+ | def send_photoshop(msg): | ||
+ | conn = socket.socket(socket.AF_INET, | ||
+ | conn.connect((HOST, | ||
+ | conn.send(msg) | ||
+ | result = conn.recv(4096) | ||
+ | conn.close() | ||
+ | print(result) | ||
+ | return result | ||
+ | |||
+ | send_photoshop(" | ||
+ | send_photoshop(" | ||
+ | send_photoshop(" | ||
+ | </ | ||
+ | * **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:// | ||
+ | * extra library required because of encrypted connection | ||
+ | * https:// | ||
+ | * https:// | ||
+ | * instruction <code python> | ||
+ | # get pyps module in photoshopConnection for connect function to PS from https:// | ||
+ | # get pbkdf2 module support encrypted TCP connection for above from https:// | ||
+ | |||
+ | import sys | ||
+ | path = ' | ||
+ | path in sys.path or sys.path.append(path) | ||
+ | path = ' | ||
+ | path in sys.path or sys.path.append(path) | ||
+ | |||
+ | from pyps import Connection, EventListener, | ||
+ | c = Connection() | ||
+ | # | ||
+ | c.connect(' | ||
+ | c.send_sync(' | ||
+ | c.send_sync(' | ||
+ | c.send_sync(' | ||
+ | res = c.send_sync(' | ||
+ | res = c.send_sync(" | ||
+ | c.close() | ||
+ | |||
+ | # it also have listerner feature, which keep listening how what Photoshop do | ||
+ | </ | ||
+ | |||
+ | * ** 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' | ||
+ | * http:// | ||
+ | * http:// | ||
+ | * https:// | ||
+ | * system command to ask Photoshop run a jsx file< | ||
+ | photoshop.exe " | ||
+ | open -b com.adobe.Photoshop --args " | ||
+ | * python code <code python module_photoshop.py> | ||
+ | ''' | ||
+ | by ying: 2016.11.22 http:// | ||
+ | note: make sure you have these empty 2 files next to it, | ||
+ | " | ||
+ | ''' | ||
+ | def ps_loc_win(): | ||
+ | import _winreg | ||
+ | regKey = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, | ||
+ | regPath = _winreg.QueryValueEx(regKey, | ||
+ | return regPath | ||
+ | |||
+ | def readFormatFile(file): | ||
+ | with open(file) as f: | ||
+ | txt = f.read() | ||
+ | return txt | ||
+ | def writeFormatFile(txt, | ||
+ | with open(file, ' | ||
+ | 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, | ||
+ | location = os.path.dirname(os.path.realpath(__file__)) | ||
+ | jsx_file = os.path.join(location, | ||
+ | result_file = os.path.join(location, | ||
+ | # step 1: record old result date | ||
+ | old_output_timestamp = os.path.getmtime(result_file) | ||
+ | | ||
+ | txt=cmd_txt | ||
+ | txt+=""" | ||
+ | var output_file = new File(' | ||
+ | output_file.open(" | ||
+ | if(typeof result !== " | ||
+ | else output_file.writeln("" | ||
+ | output_file.close(); | ||
+ | """ | ||
+ | writeFormatFile(txt, | ||
+ | # step 2: process cmd | ||
+ | if sys.platform in [' | ||
+ | ps_loc = ps_loc_win() | ||
+ | subprocess.Popen(ps_loc+" | ||
+ | elif sys.platform == ' | ||
+ | subprocess.Popen(' | ||
+ | # step 3: wait and get result | ||
+ | timeout = time.time() + 60*timer | ||
+ | while True: | ||
+ | time.sleep(0.1) | ||
+ | if time.time() > timeout: | ||
+ | print(" | ||
+ | break | ||
+ | cur_output_timestamp = os.path.getmtime(result_file) | ||
+ | if cur_output_timestamp > old_output_timestamp: | ||
+ | print(" | ||
+ | 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=' | ||
+ | result = app.activeDocument.layers.length; | ||
+ | result = " | ||
+ | """ | ||
+ | |||
+ | my_result = ps_cmd(my_cmd) | ||
+ | print(my_result) | ||
+ | |||
+ | if __name__ == " | ||
+ | main() | ||
+ | </ | ||
+ | * the quick ui tool with universal_tool_template.py, | ||
+ | * here is screenshot \\ {{: | ||
+ | |||
+ | ====== 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 < | ||
+ | * download scripting guide cs2-cc: http:// | ||
+ | * ref: | ||
+ | * http:// | ||
+ | |||
+ | * **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:// | ||
+ | * 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:// | ||
+ | * 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:// | ||
+ | * download of adobe 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:// | ||
+ | * example: http:// | ||
+ | |||
+ | * other tools using custom panel: | ||
+ | * Export kit: http:// | ||
+ | |||
+ | * More news on CC version and above: | ||
+ | * https:// | ||
+ | * http:// | ||
+ | |||
+ | * other example project | ||
+ | * cc based panel let u list jsx file and run: https:// | ||
+ | |||
+ | ====== 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/ | ||
+ | * jsx - (js v3) | ||
+ | * old: CEP panel (Common Extensibility Platform) https:// | ||
+ | * html5 | ||
+ | * new and fresh: UXP (Unified Extensibility Platform) for PS 2021+ (v22) | ||
+ | * new js support | ||
+ | * ref: https:// | ||
+ | * https:// | ||
+ | * Building Plugins for Adobe Photoshop and XD using UXP: https:// | ||
+ | * Rundown of the UXP Announcement at MAX 2020: https:// | ||
+ | |||
+ | UXP feature: | ||
+ | * modern JS, new File API |