Skip to main content
Known Participant
August 1, 2025
Answered

Straighten photos and save photos in same directory.

  • August 1, 2025
  • 1 reply
  • 2447 views

Hi, i have a bunch of photos which are all scanned with 2 or more images in 1 scan. Hoping to use the Automate script Crop and straighten photos. 

1. The result was not ok for some photos even when there was enough seperation around the edges.

2. Also it seems i need to save each cropped file.

 

Looking for a script that would do both the job (crop and save photos ). Thanks.

 

Correct answer jazz-y

Got it Jazz. Will keep in touch. Thanks.


No, rotating with liqufy does not work - the filter has flaws in face detection that do not guarantee correct frame orientation 50% of the time.

 

Are you using windows? You can install the latest version of Pytnon from the official website (check "add to PATH" when installing). This is a script that uses opencv to detect faces - this is not the most accurate method, but it worked great on the test folder. Save it to a text file, change the extension to .py, run in python:

import subprocess
import sys
import socket
import json

API_HOST = "127.0.0.1"
API_PORT_SEND = 6321
API_PORT_LISTEN = 6320


def check_module(module_name):
    try:
        __import__(module_name)
    except ImportError:
        print(f"Module {module_name} not found. Installing...")
        return install_module(module_name)
    else:
        print(f"Module {module_name} alredy installed")
        return True


def install_module(module_name):
    try:
        subprocess.check_call([sys.executable, "-m", "pip", "install", module_name])
        print(f"Module {module_name} succesfully installed!")
        return True
    except subprocess.CalledProcessError:
        return False


check_module("opencv-python")
import cv2


def detect_faces_count(image_gray):
    face_cascade = cv2.CascadeClassifier(
        cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
    )
    faces = face_cascade.detectMultiScale(image_gray, scaleFactor=1.2, minNeighbors=7)
    return len(faces)


def rotate_image(image, angle):
    if angle == 0:
        return image
    elif angle == 90:
        return cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE)
    elif angle == 180:
        return cv2.rotate(image, cv2.ROTATE_180)
    elif angle == 270:
        return cv2.rotate(image, cv2.ROTATE_90_COUNTERCLOCKWISE)


def find_correct_orientation_angle(image_path):
    image = cv2.imread(image_path)
    angles = [0, 90, 180, 270]
    best_angle = 0
    max_faces = -1

    for angle in angles:
        rotated = rotate_image(image, angle)
        gray = cv2.cvtColor(rotated, cv2.COLOR_BGR2GRAY)
        count = detect_faces_count(gray)
        print(f"faces at {angle} degrees: {count}")
        if count > max_faces:
            max_faces = count
            best_angle = angle

    return best_angle


def send_data_to_jsx(message):
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.connect((API_HOST, API_PORT_SEND))
            s.sendall(json.dumps(message).encode("utf-8"))
    except Exception as e:
        print(f"Error sending answer: {e}")


def start_local_server():
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.bind((API_HOST, API_PORT_LISTEN))
    srv.listen(1)

    print("Server running")

    while True:
        client_socket, client_address = srv.accept()
        print(f"Connection established with {client_address}")
        try:
            message = client_socket.recv(1024)
            if message:
                message = json.loads(message.decode("utf-8").rstrip("\n"))
                print(f"Recieved message: {message}")
                if message["type"] == "payload":
                    data = message["message"]
                    print(f"Processing payload {data}")
                    result = find_correct_orientation_angle(data)
                    print(f"Rotate {result}")
                    send_data_to_jsx({"type": "answer", "message": result})
                elif message["type"] == "handshake":
                    send_data_to_jsx({"type": "answer", "message": "success"})
        except Exception as e:
            print(f"Error: {e}")
            send_data_to_jsx({"type": "answer", "message": None})
            sys.exit()
        finally:
            client_socket.close()


start_local_server()

When you first run it, it will download and install the opencv library, and will be ready to work when the "Server running" message appears. In case of poor face recognition, you can read on the Internet about setting up the detectMultiScale scaleFactor and minNeighbors parameters

 

Save the second script for Photoshop in .jsx format, it saves the current file to a temporary folder, sends a request to the running python script and rotates by the angle received in the response:

#target photoshop

const API_HOST = '127.0.0.1',
    API_PORT_SEND = 6320,
    API_PORT_LISTEN = 6321,
    API_DELAY = 10000;
var OpenCV = new SDApi(API_HOST, API_PORT_SEND, API_PORT_LISTEN),
    apl = new AM('application'),
    doc = new AM('document');

try { init() } catch (e) {
    alert(e)
}

function init() {
    if (apl.getProperty('numberOfDocuments')) {
        if (OpenCV.initialize()) {
            var f = new File(Folder.temp + '/OpenCV.jpg');
            doc.saveACopy(f);
            var result = OpenCV.sendPayload(f.fsName.replace(/\\/g, '\\\\'));
            f.remove();
            if (result) doc.rotateDocument(result)
        }
    }
}

function SDApi(apiHost, portSend, portListen) {
    this.initialize = function () {
        var result = sendMessage({ type: 'handshake', message: {} }, true, API_DELAY)
        if (!result) throw new Error('cannot connect to python!')
        return true
    }
    this.exit = function () {
        sendMessage({ type: 'exit' })
    }
    this.sendPayload = function (payload) {
        var result = sendMessage({ type: 'payload', message: payload }, true, API_DELAY)
        if (result) return result['message']
        return null;
    }
    function sendMessage(o, getAnswer, delay) {
        var tcp = new Socket;
        tcp.open(apiHost + ':' + portSend, 'UTF-8')
        tcp.writeln(objectToJSON(o))
        tcp.close()
        if (getAnswer) {
            var t1 = (new Date).getTime(),
                t2 = 0;
            var tcp = new Socket;
            if (tcp.listen(portListen, 'UTF-8')) {
                for (; ;) {
                    t2 = (new Date).getTime()
                    if (t2 - t1 > delay) return null;
                    var answer = tcp.poll();
                    if (answer != null) {
                        var a = eval('(' + answer.readln() + ')');
                        answer.close();
                        return a;
                    }
                }
            }
        }
    }
    function objectToJSON(obj) {
        if (obj === null) {
            return 'null';
        }
        if (typeof obj !== 'object') {
            return '"' + obj + '"';
        }
        if (obj instanceof Array) {
            var arr = [];
            for (var i = 0; i < obj.length; i++) {
                arr.push(objectToJSON(obj[i]));
            }
            return '[' + arr.join(',') + ']';
        }
        var keys = [];
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                keys.push(key);
            }
        }
        var result = [];
        for (var i = 0; i < keys.length; i++) {
            var key = keys[i];
            var value = objectToJSON(obj[key]);
            result.push('"' + key + '":' + value);
        }
        return '{' + result.join(',') + '}';
    }
}
function AM(target, order) {
    var s2t = stringIDToTypeID,
        t2s = typeIDToStringID,
        AR = ActionReference,
        AD = ActionDescriptor;
    target = target ? s2t(target) : null;
    this.getProperty = function (property, descMode, id, idxMode) {
        property = s2t(property);
        (r = new AR).putProperty(s2t('property'), property);
        id != undefined ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id)) :
            r.putEnumerated(target, s2t('ordinal'), order ? s2t(order) : s2t('targetEnum'));
        return descMode ? executeActionGet(r) : getDescValue(executeActionGet(r), property);
    }
    this.hasProperty = function (property, id, idxMode) {
        property = s2t(property);
        (r = new AR).putProperty(s2t('property'), property);
        id ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id))
            : r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
        try { return executeActionGet(r).hasKey(property) } catch (e) { return false }
    }
    this.saveACopy = function (pth) {
        (d1 = new AD).putInteger(s2t('extendedQuality'), 12);
        d1.putEnumerated(s2t('matteColor'), s2t('matteColor'), s2t('none'));
        (d = new AD).putObject(s2t('as'), s2t('JPEG'), d1);
        d.putPath(s2t('in'), pth);
        d.putBoolean(s2t('copy'), true);
        executeAction(s2t('save'), d, DialogModes.NO);
    }
    this.rotateDocument = function (angle) {
        (r = new ActionReference()).putEnumerated(s2t("document"), s2t("ordinal"), s2t("targetEnum"));
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        d.putUnitDouble(s2t("angle"), s2t("angleUnit"), angle);
        executeAction(s2t("rotateEventEnum"), d, DialogModes.NO);
    }
    function getDescValue(d, p) {
        switch (d.getType(p)) {
            case DescValueType.OBJECTTYPE: return { type: t2s(d.getObjectType(p)), value: d.getObjectValue(p) };
            case DescValueType.LISTTYPE: return d.getList(p);
            case DescValueType.REFERENCETYPE: return d.getReference(p);
            case DescValueType.BOOLEANTYPE: return d.getBoolean(p);
            case DescValueType.STRINGTYPE: return d.getString(p);
            case DescValueType.INTEGERTYPE: return d.getInteger(p);
            case DescValueType.LARGEINTEGERTYPE: return d.getLargeInteger(p);
            case DescValueType.DOUBLETYPE: return d.getDouble(p);
            case DescValueType.ALIASTYPE: return d.getPath(p);
            case DescValueType.CLASSTYPE: return d.getClass(p);
            case DescValueType.UNITDOUBLE: return (d.getUnitDoubleValue(p));
            case DescValueType.ENUMERATEDTYPE: return { type: t2s(d.getEnumerationType(p)), value: t2s(d.getEnumerationValue(p)) };
            default: break;
        };
    }
}

It's not very elegant, but it seems to work:

 

Perhaps this will help you.

1 reply

Known Participant
August 1, 2025

Also would like to rotate the images upright position. Thanks

Legend
August 1, 2025

The problem is close to what we did here Straighten multiple layers inside photoshop , but the photos are arranged chaotically that it complicates the task.
Immediately introduce restrictions:

  • we can not recognize the orientation of the photo depending on what is depicted on it. That is, the turn must be made manually after processing
  • we will try to trim the photo at the outer border (taking into account the shadow of the scan because it is difficult to get rid of it).
  • we are not trying to trim the photos with figured perforation or ragged edges

 

First of all, we draw attention to the fact that there is dirt on the white background, it will interfere with us, so we convert the image into a smart object (so as not to lose details during further processing) and reduce the entire image to the width indicated in the RESIZE_TO = 400 constant.

We can see that the photos are slightly yellow, and the background is white-blue. Convert the image to LAB and take a yellow-blue channel from there, where the contours of the photos are clearly visible against the background:

We want to make them more discernible, but the threshold filter here is useless as the image has a low contrast and at random to find the desired value of the threshold is very difficult, so we call the filter of the levels and look at the histogram:

We're seeing two peaks. The right peak is the background, the left is the photos. We get a histogram of whole image, find a peak on the left and its boundaries:

Now we have (practically) a full contrast image and we can use the threshold filter:

Further, everything is quite simple - create a selection based on the RGB channel, convert it in path with PATH_TOLERANCE = 6, return the original size to the image and process each closed path separately. We get a number of closed paths inside the photos, so we simply ignore the selected areas bounding box of which is less than 5% of the area of the entire image (OBJECT_THRESHOLD = 5)

 

Next, we act as well as with a film – having a frame divide it with several lines with a width of 1 pixel to find the points of intersection of the background and the boundaries of the photo, as well as determine the angle of inclination. The only change is that due to the very weak contrast, I decided not to use the threshold filter, but used the color difference formula (i.e., took the background color at the reference point and then moved along the photo until the dE reached the specified threshold COLOR_DIFFERENCE = 20

In this case, there are even more potential problems than in the case of a film, so I’m not sure if it works at least one more image. Perhaps someone will offer a more elegant solution.

const RESIZE_TO = 400, // px
    LORES_EXPAND = 1, // px
    HIRES_EXPAND = 10, // px
    PATH_TOLERANCE = 6, //1-10
    OBJECT_THRESHOLD = 5, // %
    BORDER_OFFSET = 0.4, // w*const, h*const
    COLOR_DIFFERENCE = 20, // dE, CIE76, color diifference to detect border;
    LEVELS_NOISE_THRESHOLD = 50;
var apl = new AM('application'),
    doc = new AM('document'),
    lr = new AM('layer'),
    ch = new AM('channel'),
    pth = new AM('path'),
    pathComponents = new ActionList();
try {
    if (apl.getProperty('numberOfDocuments') && !doc.hasProperty('selection')) {
        var docRes = doc.getProperty('resolution'),
            docW = doc.getProperty('width') * docRes / 72,
            docH = doc.getProperty('height') * docRes / 72;
        activeDocument.suspendHistory('Get objects bounds', "fn1()");
        if (pathComponents.count) activeDocument.suspendHistory('Slice and save objects', "fn2()");
        function fn1() { app.doForcedProgress('Get objects bounds...', 'getBounds(docW, docH)') }
        function fn2() { doForcedProgress('Crop and save objects...', 'sliceObjects(pathComponents)') }
    }
}
catch (e) { alert('Sorry! An error has occurred!\n' + e) }
function getBounds(docW, docH) {
    lr.convertToSmartObject();
    doc.resizeImageToWidth(RESIZE_TO);
    doc.convertMode('labColorMode');
    doc.selectChannel('b');
    doc.makeSelection(0, 0, docW, docH);
    doc.copyPixes();
    doc.convertMode('RGBColorMode');
    doc.pastePixels();
    var levelsThreshold = findMinMax(ch.getProperty('histogram'));
    doc.levels(levelsThreshold[0], levelsThreshold[1]);
    doc.threshold(1);
    doc.makeSelectionFromChannel('RGB');
    doc.deleteLayer();
    doc.expandSelection(LORES_EXPAND);
    doc.workPathFromSelection(PATH_TOLERANCE);
    doc.resizeImageToWidth(docW);
    doc.flatten();
    largeObjects = (pth.getProperty('pathContents').value).getList(stringIDToTypeID('pathComponents'));
    for (var i = 0; i < largeObjects.count; i++) {
        pth.workPathFromDesc(largeObjects.getObjectValue(i));
        pth.selectionFromWorkPath();
        if (doc.hasProperty('selection')) {
            var bounds = doc.descToObject(doc.getProperty('selection').value),
                s = (bounds.right - bounds.left) * (bounds.bottom - bounds.top);
            if ((s / (docH * docW)) * 100 > OBJECT_THRESHOLD) pathComponents.putObject(stringIDToTypeID('pathComponent'), largeObjects.getObjectValue(i))
        }
        pth.delete();
    }
    function findMinMax(a) {
        var max = 0,
            min = 0,
            level = 0.
        for (var i = 255; i >= 0; i--) {
            var cur = a.getInteger(i)
            if (cur < LEVELS_NOISE_THRESHOLD) continue;
            if (checkPeak(a, cur, i, 10)) {
                max = cur;
                level = i;
                break;
            }
        }
        for (var i = level; i >= 0; i--) {
            if (a.getInteger(i - 1) > a.getInteger(i)) {
                min = i - 1
                break;
            }
        }
        for (var i = level; i < a.count; i++) {
            if (a.getInteger(i) == 0) {
                max = i
                break;
            }
        }
        return [min, max]
        function checkPeak(l, cur, from, steps) {
            for (var i = from; i > from - steps; i--) {
                if (l.getInteger(i) > cur) return false
            }
            return true
        }
    }
}
function sliceObjects(objects) {
    for (var i = 0; i < objects.count; i++) {
        pth.workPathFromDesc(objects.getObjectValue(i));
        pth.selectionFromWorkPath()
        var title = doc.getProperty('title').replace(/\..+$/, '') + ' ' + (i + 1),
            fld = doc.getProperty('fileReference').path;
        changeProgressText(title + '/' + objects.count)
        updateProgress((i + 1), objects.count)
        if (doc.hasProperty('selection')) {
            doc.expandSelection(HIRES_EXPAND);
            doc.duplicateLayer();
            doc.convertToSmartObject();
            doc.editSmartObject();
            pth.delete();
            var hst = activeDocument.activeHistoryState;
            doc.resizeImageToWidth(RESIZE_TO);
            var docRes = doc.getProperty('resolution'),
                tmpW = doc.getProperty('width') * docRes / 72,
                tmpH = doc.getProperty('height') * docRes / 72;
            if (tmpW < tmpH) {
                var coord1 = [tmpW - findCoordinate(getLine(tmpH * BORDER_OFFSET, 0, tmpH * BORDER_OFFSET + 1, tmpW), true), tmpH * BORDER_OFFSET],
                    coord2 = [tmpW - findCoordinate(getLine(tmpH * (1 - BORDER_OFFSET), 0, tmpH * (1 - BORDER_OFFSET) + 1, tmpW), true), (tmpH * (1 - BORDER_OFFSET))];
                activeDocument.activeHistoryState = hst;
                lr.rotate(Math.atan2(coord2[0] - coord1[0], coord2[1] - coord1[1]) * 180 / Math.PI);
            } else {
                var coord1 = [tmpW * BORDER_OFFSET, tmpH - findCoordinate(getLine(0, tmpW * BORDER_OFFSET, tmpH, tmpW * BORDER_OFFSET + 1), true)],
                    coord2 = [(tmpW * (1 - BORDER_OFFSET)), tmpH - findCoordinate(getLine(0, tmpW * (1 - BORDER_OFFSET), tmpH, tmpW * (1 - BORDER_OFFSET) + 1), true)];
                activeDocument.activeHistoryState = hst;
                lr.rotate(-(Math.atan2(coord2[1] - coord1[1], coord2[0] - coord1[0]) * 180 / Math.PI));
            }
            doc.revealAll();
            var hst = activeDocument.activeHistoryState;
            doc.resizeImageToWidth(RESIZE_TO);
            var lineX = getLine(tmpH * 0.5, 0, tmpH * 0.5 + 1, tmpW),
                left = findCoordinate(lineX),
                right = tmpW - findCoordinate(lineX, true),
                lineY = getLine(0, tmpW * 0.5, tmpH, tmpW * 0.5 + 1),
                top = findCoordinate(lineY),
                bottom = tmpH - findCoordinate(lineY, true);
            activeDocument.activeHistoryState = hst;
            var docRes = doc.getProperty('resolution'),
                docW = doc.getProperty('width') * docRes / 72,
                docH = doc.getProperty('height') * docRes / 72;
            var k = docH / tmpH;
            doc.makeSelection(top * k, left * k, bottom * k, right * k);
            doc.expandSelection(LORES_EXPAND);
            doc.crop();
            doc.flatten();
            doc.saveACopyToTIFF(new File(fld + '/' + title))
            doc.close();
            doc.deleteLayer();
        }
    }
    pth.delete();
    doc.deselect();
    function getLine(top, left, bottom, right) {
        var hst = activeDocument.activeHistoryState;
        doc.makeSelection(top, left, bottom, right);
        doc.crop();
        var f = new File(Folder.temp + '/colors.raw');
        doc.saveToRAW(f)
        activeDocument.activeHistoryState = hst;
        return readStrip(f);
        function readStrip(f) {
            var content = '';
            if (f.exists) {
                f.open('r');
                f.encoding = "BINARY";
                content = f.read();
                f.close();
                f.remove();
                return colors = function (s) {
                    var m = [],
                        offset = 0;
                    do {
                        var c = [];
                        for (var i = 0; i < 3; i++) {
                            var k = s.charCodeAt(offset + i);
                            c.push(k)
                        };
                        m.push(c)
                        offset += 3;
                    } while (offset < s.length)
                    return m;
                }(content);
            }
        }
    }
    function findCoordinate(colors, reverse) {
        if (reverse) colors.reverse();
        for (var i = 0; i < colors.length; i++) {
            if (colors[i][0] == 255 && colors[i][1] == 255 && colors[i][2] == 255) continue;
            break;
        }
        var base = colors[i];
        for (i; i < colors.length; i++) {
            if (dE(base, colors[i]) > COLOR_DIFFERENCE) return i
        }
        function dE(a, b) { return Math.sqrt(Math.pow(a[0] - b[0], 2) + Math.pow(a[1] - b[1], 2) + Math.pow(a[2] - b[2], 2)); }
    }
}
function AM(target) {
    var s2t = stringIDToTypeID,
        t2s = typeIDToStringID;
    target = target ? s2t(target) : null;
    this.getProperty = function (property, id, idxMode) {
        property = s2t(property);
        (r = new ActionReference()).putProperty(s2t('property'), property);
        id != undefined ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id)) :
            r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
        return getDescValue(executeActionGet(r), property)
    }
    this.hasProperty = function (property, id, idxMode) {
        property = s2t(property);
        (r = new ActionReference()).putProperty(s2t('property'), property);
        id ? (idxMode ? r.putIndex(target, id) : r.putIdentifier(target, id))
            : r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
        return executeActionGet(r).hasKey(property)
    }
    this.descToObject = function (d) {
        var o = {}
        for (var i = 0; i < d.count; i++) {
            var k = d.getKey(i)
            o[t2s(k)] = getDescValue(d, k)
        }
        return o
    }
    this.duplicateLayer = function () {
        executeAction(s2t("copyToLayer"), undefined, DialogModes.NO);
    }
    this.convertToSmartObject = function () {
        executeAction(s2t("newPlacedLayer"), undefined, DialogModes.NO);
    }
    this.editSmartObject = function () {
        executeAction(s2t("placedLayerEditContents"), new ActionDescriptor(), DialogModes.NO);
    }
    this.makeSelection = function (top, left, bottom, right) {
        (r = new ActionReference()).putProperty(s2t("channel"), s2t("selection"));
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        (d1 = new ActionDescriptor()).putUnitDouble(s2t("top"), s2t("pixelsUnit"), top);
        d1.putUnitDouble(s2t("left"), s2t("pixelsUnit"), left);
        d1.putUnitDouble(s2t("bottom"), s2t("pixelsUnit"), bottom);
        d1.putUnitDouble(s2t("right"), s2t("pixelsUnit"), right);
        d.putObject(s2t("to"), s2t("rectangle"), d1);
        executeAction(s2t("set"), d, DialogModes.NO);
    }
    this.flatten = function () {
        executeAction(s2t("flattenImage"), new ActionDescriptor(), DialogModes.NO);
    }
    this.threshold = function (level) {
        (d = new ActionDescriptor()).putInteger(s2t("level"), level);
        executeAction(s2t("thresholdClassEvent"), d, DialogModes.NO);
    }
    this.crop = function () {
        (d = new ActionDescriptor()).putBoolean(s2t("delete"), true);
        executeAction(s2t("crop"), d, DialogModes.NO);
    }
    this.saveToRAW = function (f) {
        (d = new ActionDescriptor()).putBoolean(s2t('copy'), true);
        (d1 = new ActionDescriptor()).putObject(s2t("as"), s2t("rawFormat"), d);
        d1.putPath(s2t("in"), f);
        executeAction(s2t("save"), d1, DialogModes.NO);
    }
    this.close = function (saving) {
        saving = saving ? saving : 'no';
        (d = new ActionDescriptor()).putEnumerated(s2t("saving"), s2t("yesNo"), s2t(saving));
        executeAction(s2t("close"), d, DialogModes.NO);
    }
    this.selectLayer = function (order) {
        (r = new ActionReference()).putEnumerated(s2t("layer"), s2t("ordinal"), s2t(order));
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        executeAction(s2t("select"), d, DialogModes.NO);
    }
    this.fill = function (color) {
        (d = new ActionDescriptor()).putEnumerated(s2t("using"), s2t("fillContents"), s2t(color));
        d.putEnumerated(s2t("mode"), s2t("blendMode"), s2t("normal"));
        executeAction(s2t("fill"), d, DialogModes.NO);
    }
    this.revealAll = function () {
        executeAction(s2t("revealAll"), new ActionDescriptor(), DialogModes.NO);
    }
    this.saveACopyToTIFF = function (pth) {
        (d1 = new ActionDescriptor()).putEnumerated(s2t("byteOrder"), s2t("platform"), s2t("IBMPC"));
        (d = new ActionDescriptor()).putObject(s2t("as"), s2t("TIFF"), d1);
        d.putPath(s2t("in"), pth);
        d.putBoolean(s2t("copy"), true);
        executeAction(s2t("save"), d, DialogModes.NO);
    }
    this.deleteLayer = function () {
        (r = new ActionReference()).putEnumerated(s2t('layer'), s2t('ordinal'), s2t('targetEnum'));
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        executeAction(s2t("delete"), d, DialogModes.NO);
    }
    this.rotate = function (angle) {
        (r = new ActionReference()).putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        d.putEnumerated(s2t("freeTransformCenterState"), s2t("quadCenterState"), s2t("QCSAverage"));
        d.putUnitDouble(s2t("angle"), s2t("angleUnit"), angle);
        d.putEnumerated(s2t("interfaceIconFrameDimmed"), s2t("interpolationType"), s2t("bicubic"));
        executeAction(s2t("transform"), d, DialogModes.NO);
    }
    this.resizeImageToWidth = function (width) {
        (d = new ActionDescriptor()).putUnitDouble(s2t("width"), s2t("pixelsUnit"), width);
        d.putBoolean(s2t("constrainProportions"), true);
        d.putEnumerated(s2t("interpolation"), s2t("interpolationType"), s2t("bicubicInterpolation"));
        executeAction(s2t("imageSize"), d, DialogModes.NO);
    }
    this.convertMode = function (mode) {
        (d = new ActionDescriptor()).putClass(s2t("to"), s2t(mode));
        d.putBoolean(s2t("merge"), false);
        d.putBoolean(s2t("rasterize"), false);
        executeAction(s2t("convertMode"), d, DialogModes.NO);
    }
    this.selectChannel = function (channel) {
        (r = new ActionReference()).putEnumerated(s2t("channel"), s2t("channel"), s2t(channel));
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        executeAction(s2t("select"), d, DialogModes.NO);
    }
    this.makeSelectionFromChannel = function (channel) {
        (r = new ActionReference()).putProperty(s2t("channel"), s2t("selection"));
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        (r1 = new ActionReference()).putEnumerated(s2t("channel"), s2t("channel"), s2t(channel));
        d.putReference(s2t("to"), r1);
        executeAction(s2t("set"), d, DialogModes.NO);
    }
    this.copyPixes = function () {
        (d = new ActionDescriptor()).putString(s2t("copyHint"), 'pixels');
        executeAction(s2t("copyEvent"), d, DialogModes.NO);
    }
    this.pastePixels = function () {
        ((d = new ActionDescriptor())).putClass(s2t("as"), s2t("pixel"));
        executeAction(s2t("paste"), d, DialogModes.NO);
    }
    this.levels = function (lo, hi) {
        (d = new ActionDescriptor()).putEnumerated(s2t("presetKind"), s2t("presetKindType"), s2t("presetKindCustom"));
        (r = new ActionReference()).putEnumerated(s2t("channel"), s2t("channel"), s2t("composite"));
        (d1 = new ActionDescriptor()).putReference(s2t("channel"), r);
        (l1 = new ActionList()).putInteger(lo);
        l1.putInteger(hi);
        d1.putList(s2t("input"), l1);
        (l = new ActionList()).putObject(s2t("levelsAdjustment"), d1);
        d.putList(s2t("adjustment"), l);
        executeAction(s2t("levels"), d, DialogModes.NO);
    }
    this.expandSelection = function (pixels) {
        (d = new ActionDescriptor()).putUnitDouble(s2t("by"), s2t("pixelsUnit"), pixels);
        d.putBoolean(s2t("selectionModifyEffectAtCanvasBounds"), false);
        executeAction(s2t("expand"), d, DialogModes.NO);
    }
    this.workPathFromSelection = function (tolerance) {
        (r = new ActionReference()).putClass(s2t("path"));
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        (r1 = new ActionReference()).putProperty(s2t("selectionClass"), s2t("selection"));
        d.putReference(s2t("from"), r1);
        d.putUnitDouble(s2t("tolerance"), s2t("pixelsUnit"), tolerance);
        executeAction(s2t("make"), d, DialogModes.NO);
    }
    this.selectionFromWorkPath = function () {
        (r = new ActionReference()).putProperty(s2t("channel"), s2t("selection"));
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        (r1 = new ActionReference()).putProperty(s2t("path"), s2t("workPath"));
        d.putReference(s2t("to"), r1);
        executeAction(s2t("set"), d, DialogModes.NO);
    }
    this.workPathFromDesc = function (desc) {
        (r = new ActionReference()).putProperty(s2t('path'), s2t('workPath'));
        (d = new ActionDescriptor()).putReference(s2t('target'), r);
        (l = new ActionList).putObject(s2t('pathComponent'), desc);
        d.putList(s2t('to'), l);
        executeAction(s2t('set'), d, DialogModes.NO);
    }
    this.delete = function () {
        (r = new ActionReference()).putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
        (d = new ActionDescriptor()).putReference(s2t("null"), r);
        executeAction(s2t("delete"), d, DialogModes.NO);
    }
    this.deselect = function () {
        (r = new ActionReference()).putProperty(s2t('channel'), s2t('selection'));
        (d = new ActionDescriptor()).putReference(s2t('null'), r);
        d.putEnumerated(s2t('to'), s2t('ordinal'), s2t('none'));
        executeAction(s2t('set'), d, DialogModes.NO);
    }
    function getDescValue(d, p) {
        switch (d.getType(p)) {
            case DescValueType.OBJECTTYPE: return { type: t2s(d.getObjectType(p)), value: d.getObjectValue(p) };
            case DescValueType.LISTTYPE: return d.getList(p);
            case DescValueType.REFERENCETYPE: return d.getReference(p);
            case DescValueType.BOOLEANTYPE: return d.getBoolean(p);
            case DescValueType.STRINGTYPE: return d.getString(p);
            case DescValueType.INTEGERTYPE: return d.getInteger(p);
            case DescValueType.LARGEINTEGERTYPE: return d.getLargeInteger(p);
            case DescValueType.DOUBLETYPE: return d.getDouble(p);
            case DescValueType.ALIASTYPE: return d.getPath(p);
            case DescValueType.CLASSTYPE: return d.getClass(p);
            case DescValueType.UNITDOUBLE: return (d.getUnitDoubleValue(p));
            case DescValueType.ENUMERATEDTYPE: return { type: t2s(d.getEnumerationType(p)), value: t2s(d.getEnumerationValue(p)) };
            default: break;
        };
    }
}

        

 

Known Participant
August 2, 2025

Thanks Jazz for the detailed explanation. Testing it on different scans. Will keep you posted. 

Best Regards.