Copy link to clipboard
Copied
Hello,
I attached a video with the details.
I've done a skew on 2 selected objects by mouse dragging, but I want to get the same result by typing an exact value (number) rather than using the mouse drag. Because it's hard to drag the mouse to an exact whole number, so I want to make it by typing a value as a number, not dragging. The problem is when I type a number, it does the skew but only to one side (I want to bring 2 corners closer to each other). But with typing it only skew the whole thing to only one side, which is not what I want.
Best,
Adam
Copy link to clipboard
Copied
You are right. I also tried with perspective wich does not require any shortcut and the problem remains the same. Changing the angle value only affect one of the rectangles…
Hope somebody will find a trick. But this meust be reported to the Photoshop team not as a bug but a deficient functionality that they could consider to enhance…
Copy link to clipboard
Copied
The difference in your result seems to be the keyboard shortcuts you are adding. What exactly is Control-Alt-Shift doing to the image? Is the Alt key making both sides move and the Shift Key constraining it? If so, then this is what we need to duplicate when typing in a number.
Michelle
Copy link to clipboard
Copied
"the Alt key making both sides move and the Shift Key constraining it"
I think so.
"this is what we need to duplicate when typing in a number."
What do you mean by that? I can make it by using the CTRL+SHIFT+ALT+MouseDrag, but I can't reach the same result by typing in a number for both sides. That's the problem. By MouseDrag it takes 1 sec to drag the 2 sides at the same time but it's painful to drag the mouse to the exact value. (almost impossible to drag the exact number I want). But I can't type in a number for both 2 sides to make the same result. You mean make a copy of one side of the object and then mirror it or rotate it? I think that's not how I want to make it.
Copy link to clipboard
Copied
The difference in your result seems to be the keyboard shortcuts you are adding. What exactly is Control-Alt-Shift doing to the image?
By @mglush
It’s a combination of the common shortcuts which work across transformation types, when dragging a handle of the transform bounding box.
Option/Alt = Transform from center
Shift = Transform proportionally
Command = Warp only one corner of the bounding box
Command+Option = Skew
Command+Option+Shift = These modifier keys seem to achieve the same thing as Edit > Transform > Perspective.
That last one seems to be the problem here:
The thread title talks about skewing, but the video does not show skewing. The result shown in the video is the same as Edit > Transform > Perspective, not Edit > Transform > Skew. But the options bar is not showing perspective values, it shows skew values. So changing a skew value will not achieve a perspective result.
It doesn’t look like the options bar offers a perspective setting. The options bar doesn’t even offer a perspective value after choosing Edit > Transform > Perspective!
So there may not be a way to apply perspective using the options bar, but I hope I’m wrong.
Copy link to clipboard
Copied
"the options bar is not showing perspective values, it shows skew values"
Yeah, you're probably right. But I can't modify the question's title from skew to perspective. Since Photoshop showed me skewing options at the bar, I couldn't decide if it's the skew or persp menu actually...
Copy link to clipboard
Copied
Since Photoshop showed me skewing options at the bar, I couldn't decide if it's the skew or persp menu actually...
By @adam2287631
Yeah, that's not your fault, when the Perspective command is chosen there should be a Perspective field up there. And it would also be nice to have a Perspective field in the options bar when the Free Transform command was chosen. This area could use some refinement and clarification for sure.
Copy link to clipboard
Copied
By virtue of the keyboard shortcut you are using you are not skewing.
Copy link to clipboard
Copied
Ok. Thanks. So what is the answer to my question?
Copy link to clipboard
Copied
That you haven’t been using the term »skew« correctly?
If you want to be able to give numerical input for perspectival transformation in Photoshop you would need to use a Script as far as I can tell.
Which seems perfectly possible (for a first transformation at least, if the object is a Smart Object that has already been transformed the issue gets »murky«), but which kind of input makes the most sense in your use-case?
An angle or a distance for the two corners or the percentage of the width in relation to the original width or …?
Please provide sample files for original and intended result.
Copy link to clipboard
Copied
Great question. Would a script work better than an action?
Michelle
Copy link to clipboard
Copied
A Script could take numerical input.
Perspectival transformation of multiple Layers seems difficult to Script though.
So the easiest way would be converting the two to one Smart Object.
Copy link to clipboard
Copied
The following Script would convert multiple selected Layers to a Smart Object and offset the top left and right corners by x pixels.
// 2024, use it at your own risk;
if (app.documents.length > 0) {
var theDist = prompt("please enter value to move the upper corners towards each other:");
if (theDist != undefined) {
var theDist = Number (theDist);
// set to pixels;
//executeAction( stringIDToTypeID( "revert" ), undefined, DialogModes.NO );
var originalRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
var selectedLayers = collectSelectedLayersBounds ();
// convert to smart object if more than one;
if (selectedLayers.length > 1) {
var theSO = convertToSmartObject ();
var theBounds = theSO[2];
} else {
// if smart object;
if (isSmartObject() == true) {var theBounds = selectedLayers[0][2]}
// if one layer but not smart object;
else {
var theSO = convertToSmartObject ();
var theBounds = theSO[2]
};
};
transformByCornerpoints ([theBounds[0]+theDist, theBounds[1]], [theBounds[2]-theDist, theBounds[1]], [theBounds[2], theBounds[3]], [theBounds[0], theBounds[3]])
// reset;
app.preferences.rulerUnits = originalRulerUnits;
};
};
////// collect bounds of selected layers //////
function collectSelectedLayersBounds () {
// set to pixels;
var originalRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
// get selected layers;
var selectedLayers = new Array;
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var desc = executeActionGet(ref);
if (desc.getBoolean(stringIDToTypeID("hasBackgroundLayer")) == true) {var theAdd =0}
else {var theAdd = 1};
if( desc.hasKey( stringIDToTypeID( 'targetLayers' ) ) ){
desc = desc.getList( stringIDToTypeID( 'targetLayers' ));
var c = desc.count;
var selectedLayers = new Array();
// run through selected layers;
for(var i=0;i<c;i++){
var theIndex = desc.getReference( i ).getIndex()+theAdd;
// get id for solid color layers;
try {
var ref = new ActionReference();
ref.putIndex( charIDToTypeID("Lyr "), theIndex );
var layerDesc = executeActionGet(ref);
var theName = layerDesc.getString(stringIDToTypeID('name'));
var theIdentifier = layerDesc.getInteger(stringIDToTypeID ("layerID"));
var theBounds = layerDesc.getObjectValue(stringIDToTypeID("bounds"));
var theseBounds = [theBounds.getUnitDoubleValue(stringIDToTypeID("left")), theBounds.getUnitDoubleValue(stringIDToTypeID("top")), theBounds.getUnitDoubleValue(stringIDToTypeID("right")), theBounds.getUnitDoubleValue(stringIDToTypeID("bottom"))];
selectedLayers.push([theName, theIdentifier, theseBounds]);
} catch (e) {};
};
// if only one:
}else{
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var layerDesc = executeActionGet(ref);
try {
var theName = layerDesc.getString(stringIDToTypeID('name'));
var theIdentifier = layerDesc.getInteger(stringIDToTypeID ("layerID"));
var theBounds = layerDesc.getObjectValue(stringIDToTypeID("bounds"));
var theseBounds = [theBounds.getUnitDoubleValue(stringIDToTypeID("left")), theBounds.getUnitDoubleValue(stringIDToTypeID("top")), theBounds.getUnitDoubleValue(stringIDToTypeID("right")), theBounds.getUnitDoubleValue(stringIDToTypeID("bottom"))];
selectedLayers = [[theName, theIdentifier, theseBounds]]
} catch (e) {};
};
// reset;
app.preferences.rulerUnits = originalRulerUnits;
return selectedLayers;
};
////// transform by corner points //////
function transformByCornerpoints (aTopLeft, aTopRight, aBottomRight, aBottomLeft) {
//////////// transformation ////////////
// from adobe’s terminology.jsx;
const classChannel = app.charIDToTypeID('Chnl');
const enumNone = app.charIDToTypeID('None');
const eventSet = app.charIDToTypeID('setd');
const eventTransform = app.charIDToTypeID('Trnf');
const keySelection = app.charIDToTypeID('fsel');
const krectangleStr = app.stringIDToTypeID("rectangle");
const kquadrilateralStr = app.stringIDToTypeID("quadrilateral");
const keyBottom = app.charIDToTypeID('Btom');
const keyLeft = app.charIDToTypeID('Left');
const keyNull = app.charIDToTypeID('null');
const keyRight = app.charIDToTypeID('Rght');
const keyTo = app.charIDToTypeID('T ');
const keyTop = app.charIDToTypeID('Top ');
const typeOrdinal = app.charIDToTypeID('Ordn');
const unitPixels = app.charIDToTypeID('#Pxl');
// from adobe’s geometry.jsx;
//
// =================================== TPoint ===================================
//
function TPoint( x, y )
{
this.fX = x;
this.fY = y;
}
// TPoint Constants
const kTPointOrigion = new TPoint( 0, 0 );
TPoint.kOrigin = kTPointOrigion;
const kTPointInfinite = new TPoint( Infinity, Infinity );
TPoint.kInfinite = kTPointInfinite;
const kTPointClassname = "TPoint";
TPoint.prototype.className = kTPointClassname;
// Overloaded math operators
TPoint.prototype["=="] = function( Src )
{
return (this.fX == Src.fX) && (this.fY == Src.fY);
}
TPoint.prototype["+"] = function( b )
{
return new TPoint( this.fX + b.fX, this.fY + b.fY );
}
TPoint.prototype["-"] = function( b, reversed )
{
if (typeof(b) == "undefined") // unary minus
return new TPoint( -this.fX, -this.fY )
else
{
if (reversed)
return new TPoint( b.fX - this.fX, by.fY - this.fY );
else
return new TPoint( this.fX - b.fX, this.fY - b.fY);
}
}
//
// Multiply and divide work with scalars as well as points
//
TPoint.prototype["*"] = function( b )
{
if (typeof(b) == 'number')
return new TPoint( this.fX * b, this.fY * b );
else
return new TPoint( this.fX * b.fX, this.fY * b.fY );
}
TPoint.prototype["/"] = function( b, reversed )
{
if (reversed)
{
if (typeof(b) == "number")
debugger;
// Can't divide a number by a point
else
return new TPoint( b.fX / this.fX, b.fY / this.fY );
}
else
{
if (typeof(b) == 'number')
return new TPoint( this.fX / b, this.fY / b );
else
return new TPoint( this.fX / b.fX, this.fY / b.fY );
}
}
TPoint.prototype.toString = function()
{
return "[" + this.fX.toString() + "," + this.fY.toString() + "]";
}
TPoint.prototype.vectorLength = function()
{
return Math.sqrt( this.fX * this.fX + this.fY * this.fY );
}
//////////// the new corners ////////////
transformActiveLayer( [new TPoint(aTopLeft[0], aTopLeft[1]), new TPoint(aTopRight[0], aTopRight[1]), new TPoint(aBottomRight[0], aBottomRight[1]), new TPoint(aBottomLeft[0], aBottomLeft[1])]);
// from adobe’s stacksupport.jsx;
// Apply a perspective transform to the current layer, with the
// corner TPoints given in newCorners (starts at top left, in clockwise order)
// Potential DOM fix
function transformActiveLayer( newCorners )
{
function pxToNumber( px )
{
return px.as("px");
}
var saveUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
var i;
var setArgs = new ActionDescriptor();
var chanArg = new ActionReference();
chanArg.putProperty( classChannel, keySelection );
// setArgs.putReference( keyNull, chanArg );
var boundsDesc = new ActionDescriptor();
var layerBounds = app.activeDocument.activeLayer.bounds;
boundsDesc.putUnitDouble( keyTop, unitPixels, pxToNumber( layerBounds[1] ) );
boundsDesc.putUnitDouble( keyLeft, unitPixels, pxToNumber( layerBounds[0] ) );
boundsDesc.putUnitDouble( keyRight, unitPixels, pxToNumber( layerBounds[2] ) );
boundsDesc.putUnitDouble( keyBottom, unitPixels, pxToNumber( layerBounds[3] ) );
// executeAction( eventSet, setArgs );
var result = new ActionDescriptor();
var args = new ActionDescriptor();
var quadRect = new ActionList();
quadRect.putUnitDouble( unitPixels, pxToNumber( layerBounds[0] ) );
// ActionList put is different from ActionDescriptor put
quadRect.putUnitDouble( unitPixels, pxToNumber( layerBounds[1] ) );
quadRect.putUnitDouble( unitPixels, pxToNumber( layerBounds[2] ) );
quadRect.putUnitDouble( unitPixels, pxToNumber( layerBounds[3] ) );
var quadCorners = new ActionList();
for (i = 0; i < 4; ++i)
{
quadCorners.putUnitDouble( unitPixels, newCorners[i].fX );
quadCorners.putUnitDouble( unitPixels, newCorners[i].fY );
}
args.putList( krectangleStr, quadRect );
args.putList( kquadrilateralStr, quadCorners );
executeAction( eventTransform, args );
// Deselect
deselArgs = new ActionDescriptor();
deselRef = new ActionReference();
deselRef.putProperty( classChannel, keySelection );
deselArgs.putReference( keyNull, deselRef );
deselArgs.putEnumerated( keyTo, typeOrdinal, enumNone );
executeAction( eventSet, deselArgs );
app.preferences.rulerUnits = saveUnits;
}
};
////// convert to smart object //////
function convertToSmartObject () {
var desc108 = new ActionDescriptor();
var ref77 = new ActionReference();
ref77.putEnumerated( charIDToTypeID( "Mn " ), charIDToTypeID( "MnIt" ), stringIDToTypeID( "newPlacedLayer" ) );
desc108.putReference( charIDToTypeID( "null" ), ref77 );
executeAction( charIDToTypeID( "slct" ), desc108, DialogModes.NO );
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var layerDesc = executeActionGet(ref);
var theName = layerDesc.getString(stringIDToTypeID('name'));
var theID = layerDesc.getInteger(stringIDToTypeID('layerID'));
var theBounds = layerDesc.getObjectValue(stringIDToTypeID("bounds"));
var theseBounds = [theBounds.getUnitDoubleValue(stringIDToTypeID("left")), theBounds.getUnitDoubleValue(stringIDToTypeID("top")), theBounds.getUnitDoubleValue(stringIDToTypeID("right")), theBounds.getUnitDoubleValue(stringIDToTypeID("bottom"))];
var theWidth = theseBounds[2]-theseBounds[0];
var theHeight = theseBounds[3]-theseBounds[1];
return [theName, theID, theseBounds, theWidth, theHeight]
};
////// is smart object //////
function isSmartObject () {
var ref = new ActionReference();
ref.putProperty (stringIDToTypeID ("property"), stringIDToTypeID ("smartObject"));
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var layerDesc = executeActionGet(ref);
var isSmartObject = layerDesc.hasKey(stringIDToTypeID("smartObject"));
return isSmartObject
};
Copy link to clipboard
Copied
I don't know if your code works, but good idea. They should integrate something like this into Photoshop too.
Copy link to clipboard
Copied
I don't know if your code works,
Then please test it.
Edit: Save the code as a txt-file, change the extension to .jsx and copy it into Photoshop’s Presets/Scripts-Folder; after restarting Photoshop it should be available under File > Scripts and can be assigned a Shortcut or used in an Action.
Copy link to clipboard
Copied
@adam2287631 , have you been able to test the Script?
Copy link to clipboard
Copied
FWIIW I almost never use the Free Transform settings. I Ctrl drag a corner handle to distort
Ctrl drag a middle handle to skew
The only time I use the X: and Y: field values is for the centre handle, like when using Step & Repeat
Unfortunately, the outside handles wont snap to guides or the grid.
Copy link to clipboard
Copied
Come to think of it …
Transforming multiple Layers via Script works out easier by grouping them and evaluating the »boundsNoEffects«.