Copy link to clipboard
Copied
Hello,
I have developed a script for Photoshop that allows users to select multiple images, which are then sequentially placed into the document. Currently, the script automatically places each image and immediately saves the psd, which doesn't allow the user to adjust the image position before saving. I aim to enhance user interaction by modifying the script to pause after each image placement, giving the user an opportunity to move the image to their desired location.
The challenge is to find a simple and efficient way for users to confirm the new position of the image and continue the script, such as pressing 'Enter' or any other key, double-clicking on the image layer, or clicking outside the document area but still within the active area of Photoshop or any other idea.
Importantly, this interaction should not use a modal dialog, as it would block further actions on the document.
Does anyone have idea how to do it?
Any advice or code examples would be greatly appreciated!
Thank you in advance for your help and suggestions!
Ah, that wasn't what I had in mind...
How about this, added to the original code directly after placing the image in the frame:
selectLayerFrameContent();
interactiveTransform();
function selectLayerFrameContent() {
var s2t = function (s) {
return app.stringIDToTypeID(s);
};
var descriptor = new ActionDescriptor();
var list = new ActionList();
var reference = new ActionReference();
reference.putName(s2t("layer"), app.activeDocument.activeLayer.name.replace(/
...
Copy link to clipboard
Copied
Are you using place to create a linked or embedded smart object in the script?
Have you looked at the preference to resize during place?
Otherwise one placed, an interactive transform command could be called.
Copy link to clipboard
Copied
Hi!
I am using the place in frame Function as we talked here: https://community.adobe.com/t5/photoshop-ecosystem-discussions/photoshop-scripting-image-placement-w...
The function:
function placeInSelectedFrame(theFile, linked) {
function s2t(s) {
return app.stringIDToTypeID(s);
}
var descriptor = new ActionDescriptor();
descriptor.putPath(s2t("null"), theFile); // File path
descriptor.putBoolean(s2t("linked"), linked); // Linked/embedded boolean
descriptor.putEnumerated(s2t("freeTransformCenterState"), s2t("quadCenterState"), s2t("QCSAverage")); // Place and fit
executeAction(s2t("placeEvent"), descriptor, DialogModes.NO);
}
Copy link to clipboard
Copied
Untested, but what if you change
DialogModes.NO
To:
DialogModes.ALL
Copy link to clipboard
Copied
It opens this dialog:
Copy link to clipboard
Copied
Ah, that wasn't what I had in mind...
How about this, added to the original code directly after placing the image in the frame:
selectLayerFrameContent();
interactiveTransform();
function selectLayerFrameContent() {
var s2t = function (s) {
return app.stringIDToTypeID(s);
};
var descriptor = new ActionDescriptor();
var list = new ActionList();
var reference = new ActionReference();
reference.putName(s2t("layer"), app.activeDocument.activeLayer.name.replace(/ Frame$/, ""));
descriptor.putReference(s2t("null"), reference);
descriptor.putBoolean(s2t("makeVisible"), false);
descriptor.putList(s2t("layerID"), list);
executeAction(s2t("select"), descriptor, DialogModes.NO);
}
function interactiveTransform() {
var s2t = function (s) {
return app.stringIDToTypeID(s);
};
var descriptor = new ActionDescriptor();
var descriptor2 = new ActionDescriptor();
descriptor.putEnumerated(s2t("freeTransformCenterState"), s2t("quadCenterState"), s2t("QCSAverage"));
descriptor2.putUnitDouble(s2t("horizontal"), s2t("pixelsUnit"), 0);
descriptor2.putUnitDouble(s2t("vertical"), s2t("pixelsUnit"), 0);
descriptor.putObject(s2t("offset"), s2t("offset"), descriptor2);
descriptor.putUnitDouble(s2t("width"), s2t("percentUnit"), 100);
descriptor.putUnitDouble(s2t("height"), s2t("percentUnit"), 100);
executeAction(s2t("transform"), descriptor, DialogModes.ALL);
}
Remove the first function if redundant.
Copy link to clipboard
Copied
That's a fantastic idea!
I'm considering using the placeInFrame function followed by interactiveTransform. I would like the interactiveTransform to specifically target the image within the frame.
Do you have any suggestions on how we can ensure that interactiveTransform applies directly to the placed image inside the frame?
Copy link to clipboard
Copied
I would like the interactiveTransform to specifically target the image within the frame.
By @Adi1231234
That's why I added:
selectLayerFrameContent();
Copy link to clipboard
Copied
After some more work on the code, I found a simpler solution that works for me to select the image inside the frame:
frame.artLayers[0]
so I call interactiveTransform on frame.artLayers[0] and it is working!!!
Thank you very much, I really appreciate your help!
Copy link to clipboard
Copied
Please post the full code as the following doesn't work:
frame.artLayers[0]
Copy link to clipboard
Copied
function interactiveTransformImageInsideFrame(frameLayer) {
app.activeDocument.activeLayer = frameLayer.artLayers[0];
var s2t = function(s) {
return app.stringIDToTypeID(s);
};
var descriptor = new ActionDescriptor();
var descriptor2 = new ActionDescriptor();
descriptor.putEnumerated(s2t("freeTransformCenterState"), s2t("quadCenterState"), s2t("QCSAverage"));
descriptor2.putUnitDouble(s2t("horizontal"), s2t("pixelsUnit"), 0);
descriptor2.putUnitDouble(s2t("vertical"), s2t("pixelsUnit"), 0);
descriptor.putObject(s2t("offset"), s2t("offset"), descriptor2);
descriptor.putUnitDouble(s2t("width"), s2t("percentUnit"), 100);
descriptor.putUnitDouble(s2t("height"), s2t("percentUnit"), 100);
try {
executeAction(s2t("transform"), descriptor, DialogModes.ALL);
} catch (e) {
// if the user approve the sive without changing anything - don't throw error
if (!e.toString().match(/User cancelled the operation/i)) {
throw e;
}
}
}
Copy link to clipboard
Copied
@Adi1231234 – thanks, I'm a bit slow this morning, I'm just not getting it.
Can you please provide a stand-alone snippet of code to select the active frame layer content so that I can see how it works?
Copy link to clipboard
Copied
In the previous response, I combined the functions for simplicity. However, if there is a preference to keep them separate, here is how it can be structured:
function selectLayerFrameContent() {
app.activeDocument.activeLayer = app.activeDocument.activeLayer.artLayers[0];
}
Copy link to clipboard
Copied
In the previous response, I combined the functions for simplicity. However, if there is a preference to keep them separate, here is how it can be structured:
function selectLayerFrameContent() { app.activeDocument.activeLayer = app.activeDocument.activeLayer.artLayers[0]; }
By @Adi1231234
Thanks, now I get it and I prefer it as a simpler alternative to my previous code for selecting the frame content!
So reversing the concept, to select the frame layer if the content is selected:
app.activeDocument.activeLayer = app.activeDocument.activeLayer.parent;
One should put in the appropriate checks to ensure that the code is only being run on a frame layer with selected content!
Copy link to clipboard
Copied
Script windows in Photoshop are modal. You can use actions or UXP instead. I simply call actions if I need user input. I also make it easy to change an image.
Example, I am a product photographer and have thousands of photos with a text overlay in the lower right corner. I run a script to create the text then call actions to position it. Included is script logic to resize the canvas which may or may not be needed, depending on the image. So if the canvas didn't need resizing, I can click three actions in succession (button mode) and change the canvas size and trext positioning. It works great and is very quick to verify finished photos.
Copy link to clipboard
Copied
Thank you for your insights and the detailed explanation! Your workflow with actions sounds very efficient, especially for managing large volumes of photos with text overlays. I am quite new to using actions in Photoshop and am intrigued by the possibility of integrating a similar approach into my workflow but have a few questions about how I might adapt it to my needs.
I'm considering a workflow where I would:
Could you clarify if it's feasible to automate these steps using actions in Photoshop? Specifically, can actions handle file dialog interactions and sequential operations on multiple images like placing and transforming within a frame? My familiarity with actions is quite limited, so any further guidance or examples of similar workflows would be highly appreciated!
Copy link to clipboard
Copied
A lot of what I do uses Extendscript and Action Manager code, which is not really for beginners. Actions are great but have limited logic available.
I have numerous scripts, this is just one example. In this case the script is written to process all open files but I could have it set to do just one and then batch from Bridge. For metadata. I use TransmissionReference to store vendor part number and description to store weight, then write the weight into the headline field because Save for Web deletes description. I have another script to fix that in the finished JPEG.
If you aren't at this level of coding then you may not be able to do what you want and may need to look for other solutions.
//Demo only, not licensed for public use
//Copyright 2024 David M. Converse
#target photoshop
testText();
function testText(){
if(documents.length > 0){
var originalDialogMode = app.displayDialogs;
app.displayDialogs = DialogModes.ERROR;
var originalRulerUnits = preferences.rulerUnits;
preferences.rulerUnits = Units.PIXELS;
var docRef = app.activeDocument;
var fileNameNoExtension = docRef.name;
//text formatting
var size1 = 96;
var size2 = 60;
var size3 = 54;
var size4 = 42;
var font1 = 'Calibri';
var font2 = 'Calibri';
var style1 = 'Bold';
var style2 = 'Regular';
var texth = 220;
var textw = 660;
try{
var vendorItem = '';
var itemWeight = '';
var newText = '';
var LayerRef = null;
var TextRef = null;
if (ExternalObject.AdobeXMPScript == undefined) ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript');
var xmp = new XMPMeta(docRef.xmpMetadata.rawData);
for(var x = 0; x < documents.length; x++){
activeDocument = documents[x];
docRef = documents[x];
fileNameNoExtension = docRef.name;
vendorItem = '';
itemWeight = '';
newText = '';
LayerRef = null;
TextRef = null;
xmp = new XMPMeta(docRef.xmpMetadata.rawData);
if(xmp.doesPropertyExist(XMPConst.NS_DC, 'description')){
itemWeight = xmp.getLocalizedText(XMPConst.NS_DC, 'description', null, 'x-default');
docRef.info.headline = itemWeight.toString();
}
if(xmp.doesPropertyExist(XMPConst.NS_PHOTOSHOP, 'TransmissionReference')){
vendorItem = xmp.getProperty(XMPConst.NS_PHOTOSHOP, 'TransmissionReference').toString();
}
if(itemWeight == null){
preferences.rulerUnits = originalRulerUnits;
app.displayDialogs = originalDialogMode;
return;
}
vendorItem = vendorItem.toUpperCase();
preferences.rulerUnits = Units.PIXELS;
for(var i = 0; i < docRef.artLayers.length; i++){
if(docRef.artLayers[i].kind == LayerKind.TEXT){
docRef.artLayers[i].remove();
}
}
fileNameNoExtension = docRef.name;
fileNameNoExtension = fileNameNoExtension.split('-');
if(fileNameNoExtension.length > 1){
fileNameNoExtension.length--;
}
fileNameNoExtension = fileNameNoExtension.join('-');
fileNameNoExtension = fileNameNoExtension.split('.');
if(fileNameNoExtension.length > 1){
fileNameNoExtension.length--;
}
fileNameNoExtension = fileNameNoExtension.join('.');
if(vendorItem == ''){
newText = fileNameNoExtension;
}
else{
newText = fileNameNoExtension + '\r(' + vendorItem + ')';
}
if((docRef.name.search('-front') != -1) || (docRef.name.search('-back') != -1)){
if(fileNameNoExtension.length > 6){
app.doAction('2000','Set 1');
}
else{
app.doAction('1800','Set 1');
}
}
LayerRef = docRef.artLayers.add();
LayerRef.kind = LayerKind.TEXT;
TextRef = LayerRef.textItem;
TextRef.kind = TextType.PARAGRAPHTEXT;
TextRef.contents = newText + '\rWeight: ' + itemWeight;
TextRef.position = new Array(425, 425);
preferences.rulerUnits = Units.POINTS;
TextRef.size = size1;
TextRef.useAutoLeading = false;
TextRef.leading = size4;
TextRef.font = font1;
TextRef.style = style1;
TextRef.justification = Justification.CENTER;
TextRef.autoKerning = AutoKernType.METRICS;
layerUpdate();
function setFormatting(start, end, fontName, fontStyle, fontSize){ //format text per input
var idsetd = app.charIDToTypeID('setd');
var action = new ActionDescriptor();
var idnull = app.charIDToTypeID('null');
var reference = new ActionReference();
var idTxLr = app.charIDToTypeID('TxLr');
var idOrdn = app.charIDToTypeID('Ordn');
var idTrgt = app.charIDToTypeID('Trgt');
reference.putEnumerated(idTxLr, idOrdn, idTrgt);
action.putReference(idnull, reference);
var idT = app.charIDToTypeID('T ');
var textAction = new ActionDescriptor();
var idTxtt = app.charIDToTypeID('Txtt');
var actionList = new ActionList();
var textRange = new ActionDescriptor();
var idFrom = app.charIDToTypeID('From');
textRange.putInteger(idFrom, start);
textRange.putInteger(idT, end);
var idTxtS = app.charIDToTypeID('TxtS');
var formatting = new ActionDescriptor();
var idFntN = app.charIDToTypeID('FntN');
formatting.putString(idFntN, fontName);
var idFntS = app.charIDToTypeID('FntS');
formatting.putString(idFntS, fontStyle);
var idSz = app.charIDToTypeID('Sz ');
var idPnt = app.charIDToTypeID('#Pnt');
formatting.putUnitDouble(idSz, idPnt, fontSize);
textRange.putObject(idTxtS, idTxtS, formatting);
actionList.putObject(idTxtt, textRange);
textAction.putList(idTxtt, actionList);
action.putObject(idT, idTxLr, textAction);
app.executeAction(idsetd, action, DialogModes.NO);
}
function layerUpdate(){
try{
preferences.rulerUnits = Units.POINTS;
TextRef.textComposer = TextComposer.ADOBESINGLELINE;
if(TextRef.size == size1){
TextRef.size = size4;
TextRef.font = font2;
TextRef.style = style2;
TextRef.useAutoLeading = false;
TextRef.leading = size4;
var l = TextRef.contents.split(/\r/);
setFormatting(0, l[0].length, font1, style1, size1);
var textFragmentLen = l[0].length + l[1].length + 1;
if(l.length > 2){
setFormatting(textFragmentLen, (textFragmentLen + l[2].length -8), font1, style1, size4);
}
else{
var sectextFragmentLen = l[1].split(':');
setFormatting((textFragmentLen - l[1].length), l[0].length + sectextFragmentLen[0].length + 2, font1, style1, size4);
}
preferences.rulerUnits = Units.PIXELS;
if(TextRef.kind == TextType.PARAGRAPHTEXT){
TextRef.height = texth;
TextRef.width = textw;
TextRef.useAutoLeading = false;
TextRef.leading = size4;
}
app.doAction('Align title', 'Set 1');
app.doAction('Save', 'Set 1');
app.doAction('SFW', 'Set 1');
}
}
catch(e){
alert(e + ' ' + e.line);
return;
}
}
}
}
catch(e){
preferences.rulerUnits = originalRulerUnits;
app.displayDialogs = originalDialogMode;
return;
}
preferences.rulerUnits = originalRulerUnits;
app.displayDialogs = originalDialogMode;
}
else{
alert('You must have a document open to run this script.');
return;
}
}
Copy link to clipboard
Copied
Thank you very much for your detailed response.
While it was not directly related to the solution I was looking for, I started playing with the code you wrote and it gave me some new ideas!
I appreciate your input—it's been really insightful