[PS CC-CC2022] Автоматизация в Фотошопе

jazzy

Участник
Топикстартер
Сообщения
375
Реакции
212
Всем привет!
Меня зовут Дмитрий. Если вы интересуетесь темой скриптов, то, возможно мы с вами уже знакомы по форуму поддержки Adobe (ссылка на мой профиль в подписи). В свободное от основной работы время я с большим удовольствием помогаю там пользователям с решением их задач и за пару лет у меня накопилось множество сниппетов (коротких кусков кода, решающих ту или иную специфичную задачу).
В этой теме я буду публиковать самые интересные задачи. Возможно кому-то эти скрипты пригодятся целиком, а кто-то возьмет часть кода за основу для собственных проектов. Если у вас есть интересные идеи автоматизации Фотошопа или Бриджа или вы готовы поделиться своими скриптами - буду только рад.

Я не гарантирую, что каждый сниппет будет работать во всех версиях Фотошопа, но с большинство примеров без проблем работает во всех версиях CC (2014 и новее). Большая часть сниппетов содержит ссылку на тему, где они обсуждались (иногда контекст задачи важен не менее, чем ее решение), также (по возможности) буду прикреплять видео с примером работы кода.
 
Actions - clear actions palette
Код:
/**Быстрая очистка палитры операций (удаление всех групп экшенов)
 * (убедитесь, что все экшены на палитре заранее сохранены на диск)*/

#target photoshop
s2t = stringIDToTypeID;
while (true) {
    (r = new ActionReference()).putIndex(s2t('actionSet'), 1);
    (d = new ActionDescriptor()).putReference(s2t('null'), r);
    try { executeAction(s2t('delete'), d, DialogModes.NO) } catch (e) { break }
}

 
  • Спасибо
Реакции: NatalieRedFox_333
Actions - play set
Код:
/**Последовательный запуск группы экшенов для активного документа (с возможностью выбора группы)
 * community.adobe.com/t5/photoshop-ecosystem-discussions/script-to-play-all-actions-in-a-set-against-a-single-image/m-p/11864769
 */

#target photoshop
var s2t = stringIDToTypeID,
    w = new Window("dialog {text: 'Select action set', orientation: 'column', alignChildren: ['center','top']}"),
    l = w.add("listbox{helpTip: 'doble click to play all actions', preferredSize: [250, 200]}"),
    g = w.add("group{orientation: 'row', alignChildren: ['left', 'center']}"),
    ok = g.add("button", undefined, 'Play all actions', { name: 'ok' }),
    cancel = g.add("button", undefined, 'Cancel', { name: 'cancel' }),
    idx = 1;
while (true) {
    (r = new ActionReference()).putIndex(s2t('actionSet'), idx++);
    try { l.add('item', executeActionGet(r).getString(s2t('name'))) } catch (e) { break; }
}
l.selection = 0; ok.enabled = l.items.length;
l.onClick = function () { ok.enabled = this.selection ? true : false };
ok.onClick = function () { l.onDoubleClick() }
l.onDoubleClick = function () {
    if (idx = this.selection.index + 1) {
        w.close();
        (r = new ActionReference()).putIndex(s2t('actionSet'), idx);
        var len = executeActionGet(r).getInteger(s2t('numberOfChildren'));
        for (var i = 1; i <= len; i++) {
            (r = new ActionReference()).putIndex(s2t('action'), i);
            r.putIndex(s2t('actionSet'), idx);
            (d = new ActionDescriptor()).putReference(s2t('target'), r);
            try { executeAction(s2t('play'), d) } catch (e) { }
        }
    }
}
w.show();

 
  • Спасибо
Реакции: NatalieRedFox_333
Actions - set looper
Код:
/**Последовательный запуск экшенов из одной группы для активного документа
 * с возможностью указания от какой и до какой операции проигрывать
 * (скрипт предназначен для записи в экшен, при автономном запуске бесполезен)
 * https://community.adobe.com/t5/photoshop-ecosystem-discussions/script-to-play-all-actions-in-a-set-against-a-single-image/m-p/11866609
 */
#target photoshop
/*
<javascriptresource>
<name>Play all actions</name>
<eventid>957c6aae-60f7-49d7-817a-f93d6c2378ef</eventid>
<terminology><![CDATA[<< /Version 1
                       /Events <<
                       /957c6aae-60f7-49d7-817a-f93d6c2378ef [(Play all actions) <<
                       >>]
                        >>
                     >> ]]></terminology>
</javascriptresource>
*/
var s2t = stringIDToTypeID,
    isCancelled = false,
    u;
if (!playbackParameters.count) {
    (d = new ActionDescriptor()).putString(s2t('target'), 'play from next action');
    if (selectMode(d) == 1) { playbackParameters = d } else { isCancelled = true }
} else {
    d = playbackParameters;
    if (playbackDisplayDialogs == DialogModes.ALL) {
        if (selectMode(d) == 1) { playbackParameters = d }
    } else if (playbackDisplayDialogs != DialogModes.ALL) {
        $.setenv('stop', (d.getString(s2t('target')) == 'stop here'))
        set = getActionIdx()
        for (var i = set.atnIdx; i <= set.len; i++) {
            if ($.getenv('stop') == 'true') {break; }
            (r = new ActionReference()).putIndex(s2t('action'), i);
            r.putIndex(s2t('actionSet'), set.setIdx);
            (d = new ActionDescriptor()).putReference(s2t('target'), r);
            try { executeAction(s2t('play'), d) } catch (e) { }
        }
    }
}
isCancelled ? 'cancel' : u
function selectMode(d) {
    w = new Window("dialog {text: 'Play all actions', orientation: 'column', alignChildren: ['left','top']}"),
        p = w.add("radiobutton{text: 'play from next action'}"),
        s = w.add("radiobutton{text: 'stop here'}"),
        g = w.add("group{orientation: 'row', alignChildren: ['left', 'center']}"),
        ok = g.add("button", u, 'Save settings', { name: 'ok' }),
        cancel = g.add("button", u, 'Cancel', { name: 'cancel' });
    p.value = (d.getString(s2t('target')) == p.text)
    s.value = (d.getString(s2t('target')) == s.text)
    p.onClick = function () { d.putString(s2t('target'), this.text) }
    s.onClick = function () { d.putString(s2t('target'), this.text) }
    return w.show();
}
function getActionIdx() {
    (r = new ActionReference()).putEnumerated(s2t('action'), s2t('ordinal'), s2t('targetEnum'));
    command = executeActionGet(r);
    return getSetIdx(command.getInteger(s2t('parentIndex')), command.getString(s2t('parentName')))
    function getSetIdx(atnIdx, atnName) {
        var setIdx = 1
        while (true) {
            (r = new ActionReference()).putIndex(s2t('actionSet'), setIdx)
            try { d = executeActionGet(r) } catch (e) { break; }
            var numberOfChildren = d.hasKey(s2t('numberOfChildren')) ? d.getInteger(s2t('numberOfChildren')) : 0
            if (numberOfChildren > 0 && atnIdx <= numberOfChildren) {
                (r = new ActionReference()).putProperty(s2t('property'), s2t('name'));
                r.putIndex(s2t('action'), atnIdx);
                r.putIndex(s2t('actionSet'), setIdx);
                if (executeActionGet(r).getString(s2t('name')) == atnName) { return { setIdx: setIdx, len: numberOfChildren, atnIdx: atnIdx + 1 } }
            }
            setIdx++
        }
        return null
    }
}
 
  • Спасибо
Реакции: NatalieRedFox_333
Document - check margins
Код:
/**Проверка документа на наличие белой рамки
 * (указыается размер рамки для проверки + максимальное отклонение)
 * https://community.adobe.com/t5/photoshop-ecosystem-discussions/check-the-document-for-a-margin-or-border/td-p/12563008
 */

#target photoshop
var border = 5, //mm
    threshold = 0.1, // deviation of the size of the border on each side 
    doc = new AM('document'),
    res = doc.getProperty('resolution'),
    dW = doc.getProperty('width') * res / 72,
    dH = doc.getProperty('height') * res / 72;
doc.makeSelectionFromChannel('RGB')
doc.inverseSelection()
var lr = doc.descToObject(doc.getProperty('selection').value),
    margins = {
        left: lr.left / res * 25.4,
        top: lr.top / res * 25.4,
        right: (dW - lr.right) / res * 25.4,
        bottom: (dH - lr.bottom) / res * 25.4
    },
    err = (function (o, size) {
        for (var a in o) { if (Math.abs(size - o[a]) > threshold) return true };
        return false;
    })(margins, border);
if (!err) alert('Ok!') else alert('Not ok!\n' + margins.toSource())
doc.deselect()
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'));
        try { return executeActionGet(r).hasKey(property) } catch (e) { return false }
    }
    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.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.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);
    }
    this.inverseSelection = function () {
        executeAction(s2t('inverse'), undefined, DialogModes.NO);
    }
    function getDescValue(d, k) {
        switch (d.getType(k)) {
            case DescValueType.OBJECTTYPE: return { type: t2s(d.getObjectType(k)), value: d.getObjectValue(k) };
            case DescValueType.LISTTYPE: return d.getList(k);
            case DescValueType.REFERENCETYPE: return d.getReference(k);
            case DescValueType.BOOLEANTYPE: return d.getBoolean(k);
            case DescValueType.STRINGTYPE: return d.getString(k);
            case DescValueType.INTEGERTYPE: return d.getInteger(k);
            case DescValueType.LARGEINTEGERTYPE: return d.getLargeInteger(k);
            case DescValueType.DOUBLETYPE: return d.getDouble(k);
            case DescValueType.ALIASTYPE: return d.getPath(k);
            case DescValueType.CLASSTYPE: return d.getClass(k);
            case DescValueType.UNITDOUBLE: return (d.getUnitDoubleValue(k));
            case DescValueType.ENUMERATEDTYPE: return { type: t2s(d.getEnumerationType(k)), value: t2s(d.getEnumerationValue(k)) };
            default: break;
        };
    }
}
 
  • Спасибо
Реакции: NatalieRedFox_333
Documents - duplicate
Код:
/*Создание копии текущего документа с использованием того же имени*/

#target photoshop
(r = new ActionReference()).putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
(d = new ActionDescriptor()).putReference(stringIDToTypeID("null"), r);
d.putString(stringIDToTypeID("name"), activeDocument.name);
executeAction(stringIDToTypeID("duplicate"), d, DialogModes.NO);
 
  • Спасибо
Реакции: NatalieRedFox_333
Documents - Image orientation count
Код:
/**Вывод информации размеров и ориентации открытых документов с возможностью быстрого переключения между ними
 * цветовыми индикаторами обозначается цветовое пространство документа - CMYK (зеленый), RGB - красный.
 * https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-to-get-all-open-documnet-dimensions-with-one-alert/m-p/12548870
 */
#target photoshop
var s2t = stringIDToTypeID,
    t2s = typeIDToStringID,
    w = new Window("dialog {text: 'Window with the open files in the program'}"),
    l = w.add("listbox{preferredSize: [500, 400]}"),
    b = w.add("button {text:'Ok'}", undefined, undefined, { name: "ok" }),
    icoRed = "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\n\x00\x00\x00\n\b\x06\x00\x00\x00\u008D2\u00CF\u00BD\x00\x00\x00;IDAT\x18\u0095c\u00FCci\u00F3\u009F\u0081\b\u00C0\x02R\u00C2((\u0084W\u00E5\u00FF\u00F7\u00EF \n\u00C1\u0080\u0087\x17\u00BB\u00AA/\u009F\u00C1\x14\x131\u00D6\x0E\x15\u0085\b_C}\u0087W!(\u009C\u00F0\x02\x06\x06\x06\x00\x18\u00EF\fO\u0083\b\u00CC\u00FD\x00\x00\x00\x00IEND\u00AEB`\u0082",
    icoGreen = "\u0089PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\n\x00\x00\x00\n\b\x06\x00\x00\x00\u008D2\u00CF\u00BD\x00\x00\x00=IDAT\x18\u0095c\u00F4\u00DEo\u00F8\u009F\u0081\b\u00C0\x02R\"\u00C6\u00C9\u008FW\u00E5\u00AB\u00EF\x1F!\nA@\u0090\u009D\x1B\u00AB\u00A2\u00F7?\u00BF\u0082i&b\u00AC\x1D*\n\u00E1\u00BE\u0086\u00F9\x0E\u00AFBP8\u00E1\x05\f\f\f\x000\x1F\x0E\x05z4V\u0094\x00\x00\x00\x00IEND\u00AEB`\u0082";
l.onClick = function () {
    if (l.items.length) {
        (r = new ActionReference()).putIndex(s2t('document'), l.selection + 1);
        (d = new ActionDescriptor()).putReference(s2t('null'), r);
        executeAction(s2t('select'), d, DialogModes.NO)
    }
}
l.onDoubleClick = function () { w.close() }
w.onShow = function () {
    l.graphics.font = "Tahoma-Bold:14";
    var len = getPropertyDesc('application', p = 'numberOfDocuments').getInteger(s2t(p));
    if (len) {
        for (var i = 1; i <= len; i++) {
            var res = getPropertyDesc('document', p = 'resolution', i).getDouble(s2t(p))
            l.add('item',
                getPropertyDesc('document', p = 'title', i).getString(s2t(p)) +
                ' @ ' + Math.round(getPropertyDesc('document', p = 'zoom', i).getDouble(s2t(p)) * 100, 2) + '% ' +
                Math.round(getPropertyDesc('document', p = 'width', i).getDouble(s2t(p)) * res / 72 / res * 25.4) + 'x' +
                Math.round(getPropertyDesc('document', p = 'height', i).getDouble(s2t(p)) * res / 72 / res * 25.4) + 'mm ' +
                (mode = t2s(getPropertyDesc('document', p = 'mode', i).getEnumerationValue(s2t(p))).replace(new RegExp('Color(Enum)?'), '')) + '/' +
                getPropertyDesc('document', p = 'depth', i).getInteger(s2t(p))
            )
            for(var prop in l.items[i - 1].__proto__) alert(prop)
            l.items[i - 1].image = mode == 'CMYK' ? icoGreen : icoRed
        }
        l.selection = getPropertyDesc('document', p = 'itemIndex').getInteger(s2t(p)) - 1
    }
}
w.show();
function getPropertyDesc(target, property, idx) {
    target = s2t(target);
    (r = new ActionReference()).putProperty(s2t('property'), s2t(property));
    idx ? r.putIndex(target, idx) : r.putEnumerated(target, s2t('ordinal'), s2t('targetEnum'));
    return executeActionGet(r);
}
 
  • Спасибо
Реакции: NatalieRedFox_333
Documents - random crop
Код:
/**Произвольное кадрирование документа (для создания превью фрагмента заданного размера и заданного процента перекрытия)
 * с возможностью нанесения водяного знака (из отдельного файла)
 * https://community.adobe.com/t5/photoshop-ecosystem-discussions/random-crop-batch-processing-script/m-p/12468477
 */

#target photoshop;
randomSquareCropAndSave(10, 1000, 'e:/Path to watermark.png')
function randomSquareCropAndSave(coveragePercent, size, watermark) {
    try {
        try {
            var doc = new AM(p = 'document'),
                docRes = doc.getProperty(p = 'resolution'),
                docWidth = doc.getProperty(p = 'width') / 72 * docRes,
                docHeight = doc.getProperty(p = 'height') / 72 * docRes,
                docPath = doc.getProperty(p = 'fileReference'),
                quardSide = parseInt(Math.sqrt((coveragePercent / 100) * docWidth * docHeight) / 2);
        } catch (e) { throw ('Document property "' + p + '" not found!\n\n' + e) }
        if (quardSide * 2 > docWidth || quardSide * 2 > docHeight) throw ('Cropping area is larger than the document area!')
        var centerX = parseInt((quardSide + Math.random() * (docWidth - quardSide * 2))),
            centerY = parseInt((quardSide + Math.random() * (docHeight - quardSide * 2)));
        doc.makeNewCopy()
        doc.crop(centerY - quardSide, centerX - quardSide, centerY + quardSide, centerX + quardSide, size, size, docRes)
        if (watermark && File(watermark.exists)) doc.place(File(watermark))
        doc.saveAsJpg(function (fle, ext) {
            var uniqueFileName = fle + ext,
                fileNumber = 1;
            if (!Folder((File(fle).path)).exists) Folder((File(fle).path)).create()
            while (File(uniqueFileName).exists) {
                uniqueFileName = fle + "-" + fileNumber + ext;
                fileNumber++;
            }
            return File(uniqueFileName);
        }(decodeURI(docPath.path) + '/' + decodeURI(docPath.name).replace(/\..+$/, '') + '/' + decodeURI(docPath.name).replace(/\..+$/, ''), '-1.jpg'), 12);
        doc.close();
    } catch (e) { alert(e) }
}
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.makeNewCopy = function () {
        (r = new ActionReference()).putClass(s2t('document'));
        (d = new ActionDescriptor()).putReference(s2t('target'), r);
        (r1 = new ActionReference()).putProperty(s2t('historyState'), s2t('currentHistoryState'));
        d.putReference(s2t('using'), r1);
        executeAction(s2t('make'), d, DialogModes.NO);
    }
    this.crop = function (top, left, bottom, right, width, height, resolution) {
        (d = new ActionDescriptor()).putUnitDouble(s2t('top'), s2t('pixelsUnit'), top);
        d.putUnitDouble(s2t('left'), s2t('pixelsUnit'), left);
        d.putUnitDouble(s2t('bottom'), s2t('pixelsUnit'), bottom);
        d.putUnitDouble(s2t('right'), s2t('pixelsUnit'), right);
        (d1 = new ActionDescriptor()).putObject(s2t('to'), s2t('rectangle'), d);
        d1.putUnitDouble(s2t("angle"), s2t("angleUnit"), 0);
        d1.putBoolean(s2t('delete'), true);
        d1.putEnumerated(s2t('cropAspectRatioModeKey'), s2t('cropAspectRatioModeClass'), s2t('targetSize'));
        d1.putUnitDouble(s2t('width'), s2t('pixelsUnit'), width);
        d1.putUnitDouble(s2t('height'), s2t('pixelsUnit'), height);
        d1.putUnitDouble(s2t('resolution'), s2t('densityUnit'), resolution);
        executeAction(s2t('crop'), d1, DialogModes.NO);
    }
    this.saveAsJpg = function (pth, quality) {
        (d = new ActionDescriptor()).putInteger(s2t('extendedQuality'), quality);
        d.putEnumerated(s2t('matteColor'), s2t('matteColor'), s2t('none'));
        (d1 = new ActionDescriptor()).putObject(s2t('as'), s2t('JPEG'), d);
        d1.putPath(s2t('in'), pth);
        executeAction(s2t('save'), d1, DialogModes.NO);
    }
    this.close = function () {
        (d = new ActionDescriptor()).putEnumerated(s2t('saving'), s2t('yesNo'), s2t('no'));
        executeAction(s2t('close'), d, DialogModes.NO)
    }
    this.place = function (pth) {
        (d = new ActionDescriptor()).putPath(s2t("null"), pth);
        executeAction(s2t("placeEvent"), 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;
        };
    }
}
 
  • Спасибо
Реакции: NatalieRedFox_333
Documents - remember selected document
Код:
/**Запоминание текущего открытого документа и последующий возврат к нему
 * (скрипт предназначен для записи в экшен, но может использоваться и в автономном режиме)
 * https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-to-change-the-order-of-open-documents-for-an-action/m-p/12438243
 */

#target photoshop
/*
<javascriptresource>
<name>Target document</name>
<eventid>a4a27371-cedc-4af8-a6d1-f25765eca0cb</eventid>
<enableinfo>true</enableinfo>
<terminology><![CDATA[<< /Version 1
                         /Events <<
                          /a4a27371-cedc-4af8-a6d1-f25765eca0cb [(Target document)<<
                          /select [(Mode) /string]
                          >>]
                         >>
                      >> ]]></terminology>
</javascriptresource>
*/
var s2t = stringIDToTypeID,
    isCancelled = false,
    UUID = 'a4a27371-cedc-4af8-a6d1-f25765eca0cb',
    id = null;
const REMEMBER = 'remebmer current document',
    SELECT = "select remembered document";
try { d = getCustomOptions(UUID) } catch (e) { }
if (d != undefined) id = d.getInteger(s2t('documentID'))
if (!app.playbackParameters.count) {
    var w = buildWindow(), result = w.show()
    switch (result) {
        case 0: saveSettings(result, true, true); break;
        case 1: selectDocumentByID(id, true); saveSettings(result, false, true); break;
        default: isCancelled = true;
    }
}
else {
    var d = app.playbackParameters,
        mode = d.getString(s2t('select'));
    if (app.playbackDisplayDialogs == DialogModes.ALL) {
        var w = buildWindow(mode == SELECT), result = w.show()
        if (result == 2) { isCancelled = true } else {
            if (result) {
                selectDocumentByID(id)
                saveSettings(result, false, true)
            } else {
                saveSettings(result, true, true)
            }
        }
    }
    if (app.playbackDisplayDialogs != DialogModes.ALL) {
        if (mode == SELECT) selectDocumentByID(id) else {
            saveSettings(0, true, false)
        }
    }
}
isCancelled ? 'cancel' : undefined
function buildWindow(sel) {
    var u = undefined,
        d = new Window("dialog {text: 'Target document' }"),
        dl = d.add("dropdownlist", u, u, { items: [REMEMBER, SELECT] }),
        g = d.add("group{orientation : 'row'}"),
        bnOk = g.add("button {text:'Ok'}", u, u, { name: "ok" }),
        bnCancel = g.add("button {text : 'Cancel'}", u, u, { name: "cancel" });
    dl.selection = sel != undefined ? sel : 0;
    bnOk.onClick = function () { w.close(dl.selection.index) }
    return d;
}
function saveSettings(mode, renewId, renewMode) {
    if (renewId) putCustomOptions(UUID, getDocumentID(), false);
    if (renewMode) {
        (d = new ActionDescriptor()).putString(s2t('select'), mode ? SELECT : REMEMBER);
        playbackParameters = d;
    }
}
function selectDocumentByID(id, silentMode) {
    if (id) {
        (r = new ActionReference()).putIdentifier(s2t('document'), id);
        (d = new ActionDescriptor()).putReference(s2t('target'), r);
        try { executeAction(s2t('select'), d, DialogModes.NO) } catch (e) { if (!silentMode) alert('Remembered document is not avaliable!') }
    }
    else { alert('No remembered document!') }
}
function getDocumentID() {
    (r = new ActionReference()).putProperty(s2t('property'), p = s2t('documentID'));
    r.putEnumerated(s2t('document'), s2t('ordinal'), s2t('targetEnum'));
    return executeActionGet(r);
}
 
  • Спасибо
Реакции: NatalieRedFox_333
Events - auto new layer
Код:
/**Автоматическое создание нового слоя при попытке рисововать кистью на
 * смарт-объекте, настраиваемом или текстовом слое
 *
 * Скрипт работает с подсистемой событий Фотошопа, поэтому
 * запуск непосредственно из редактора кода невозможен - скрипт
 * обязательно должен быть сохранен на диске.
*/

#target photoshop
var s2t = stringIDToTypeID,
    t2s = typeIDToStringID;
try { var evt = arguments[0] } catch (e) { }
if (evt) {
    try {
        if (evt.hasKey(s2t('title'))) {
            if (evt.getString(s2t('title')) == 'Alert') {
                if (evt.hasKey(s2t('state'))) {
                    if (t2s(evt.getEnumerationValue(s2t('state'))) == 'exit') {
                        (r = new ActionReference()).putProperty(s2t('property'), p = s2t('tool'));
                        r.putEnumerated(s2t('application'), s2t('ordinal'), s2t('targetEnum'));
                        var tool = t2s(executeActionGet(r).getEnumerationType(s2t('tool')));
                        (r = new ActionReference()).putProperty(s2t('property'), p = s2t('layerKind'));
                        r.putEnumerated(s2t('layer'), s2t('ordinal'), s2t('targetEnum'));
                        var kind = executeActionGet(r).getInteger(p);
                        if (tool == 'paintbrushTool' && (kind == 2 || kind == 3 || kind == 5 )) {
                            (r = new ActionReference()).putClass(s2t("layer"));
                            (d = new ActionDescriptor()).putReference(s2t("null"), r);
                            executeAction(s2t("make"), d, DialogModes.NO);
                        }
                    }
                }
            }
        }
    } catch (e) { }
} else {
    app.notifiersEnabled = true
    var f = File($.fileName)
    app.notifiers.add('modalStateChanged', f)
}
 
  • Спасибо
Реакции: NatalieRedFox_333
Events - get current workspace
Код:
/**Слежение за переключением текущей рабочей области и выполнение той или иной функции в завимости от ее имени
 * (в скрипте используются рабочие области "рисование" и "фотография")
 *
 * Скрипт работает с подсистемой событий Фотошопа, поэтому
 * запуск непосредственно из редактора кода невозможен - скрипт
 * обязательно должен быть сохранен на диске.
 *
 * https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-to-determine-the-name-of-the-active-work-environment/m-p/12150059
 */

// код слежения за состоянием рабочего пространства
#target photoshop
var s2t = stringIDToTypeID,
    t2s = typeIDToStringID,
    UUID = 'bf7064f7-eee2-4ac2-a639-5c3832469b43';
try {
    var target = t2s(arguments[0].getReference(s2t('null')).getDesiredClass())
    if (target == 'workspace') {
        var d = new ActionDescriptor();
        d.putString(s2t('workspace'), arguments[0].getReference(s2t('null')).getName())
        alert('Активировано рабочее пространство ' + arguments[0].getReference(s2t('null')).getName())
        app.putCustomOptions(UUID, d)
    }
} catch (e) {
    app.notifiersEnabled = true
    var f = File($.fileName),
        deleted;
    for (var i = 0; i < app.notifiers.length; i++) {
        var ntf = app.notifiers[i]
        if (ntf.eventFile.name == f.name) { ntf.remove(); i--; deleted = true }
    }
    if (deleted) {
        alert('event listening disabled!')
    } else {
        alert('event listening enabled!')
        app.notifiers.add('slct', f)
        var r = new ActionReference();
        r.putProperty(s2t('property'), s2t('menuBarInfo'));
        r.putEnumerated(s2t('application'), s2t('ordinal'), s2t('targetEnum'));
        var mainMenu = executeActionGet(r).getObjectValue(s2t('menuBarInfo')).getList(s2t('submenu'))
        var windowMenu = new ActionDescriptor()
        for (var i = 0; i < mainMenu.count; i++) {
            if (mainMenu.getObjectValue(i).getString(s2t('title')) == localize('$$$/Menu/Window')) {
                var windowMenu = mainMenu.getObjectValue(i).getList(s2t('submenu'))
                break;
            }
        }
        var workspaceMenu = new ActionDescriptor()
        for (var i = 0; i < windowMenu.count; i++) {
            if (windowMenu.getObjectValue(i).getString(s2t('title')) == localize('$$$/Menu/Window/ProjectWorkSpace')) {
                workspaceMenu = windowMenu.getObjectValue(i).getList(s2t('submenu'))
                break;
            }
        }
        for (var i = 0; i < workspaceMenu.count; i++) {
            if (workspaceMenu.getObjectValue(i).getBoolean(s2t('checked'))) {
                var d = new ActionDescriptor();
                d.putString(s2t('workspace'), workspaceMenu.getObjectValue(i).getString(s2t('title')))
                app.putCustomOptions(UUID, d)
                break;
            }
        }
    }
}

// получение данных о рабочем пространстве:
/*
var s2t = stringIDToTypeID,
    UUID = 'bf7064f7-eee2-4ac2-a639-5c3832469b43';
try { var d = app.getCustomOptions(UUID) } catch (e) { }
if (d) var w = d.getString(s2t('workspace'))
if (w == localize('$$$/FileName/Presets/WorkSpaces/Photography')) {
    // call function for photography workspace here
    alert ('В данный момент активно рабочее пространство Фотография!')
} else if (w == localize('$$$/FileName/Presets/WorkSpaces/Painting')) {
    // call function for painting workspace here
    alert ('В данный момент активно рабочее пространство Рисование!')
}
*/
 
  • Спасибо
Реакции: NatalieRedFox_333
Events - rasterize shape
Код:
/**Растеризация объекта типа shape после окончания процесса рисования (по нажатию Enter)
 *
 * Скрипт работает с подсистемой событий Фотошопа, поэтому
 * запуск непосредственно из редактора кода невозможен - скрипт
 * обязательно должен быть сохранен на диске.
 *
 * https://community.adobe.com/t5/photoshop/javascript-script-trigger-action-through-enter-key-of-path-deselection/m-p/11573965
 */

#target photoshop
s2t = stringIDToTypeID,
    t2s = typeIDToStringID;
try { var evt = arguments[0] } catch (e) { }
if (evt) {
    if (app.currentTool == "penTool") {
        app.activeDocument.activeLayer.rasterize(RasterizeType.ENTIRELAYER);
    }
} else {
    app.notifiersEnabled = true
    var f = File($.fileName)
    app.notifiers.add('slct', f, 'Path')
}
 
  • Спасибо
Реакции: NatalieRedFox_333
Layers - apply action to selected
Код:
/**Применение одного и того же экшена к выделенным слоям в документе
 * https://community.adobe.com/t5/photoshop-ecosystem-discussions/applying-an-action-to-all-the-layers-in-a-document/m-p/12463566
 */

#target photoshop
var s2t = stringIDToTypeID;
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('targetLayersIDs'));
r.putEnumerated(s2t('document'), s2t('ordinal'), s2t('targetEnum'));
var lrs = executeActionGet(r).getList(p);
(r = new ActionReference()).putEnumerated(s2t('action'), s2t('ordinal'), s2t('targetEnum'));
try {
    try {
        var atn = executeActionGet(r).getString(s2t('name')),
            set = executeActionGet(r).getString(s2t('parentName'));
    }
    catch (e) { throw 'Before start select any action from actions palette!' }
    for (var i = 0; i < lrs.count; i++) {
        (r = new ActionReference()).putIdentifier(s2t('layer'), lrs.getReference(i).getIdentifier(s2t('layerID')));
        (d = new ActionDescriptor()).putReference(s2t('target'), r);
        try { executeAction(s2t('select'), d, DialogModes.NO); } catch (e) { throw e + '\nCannot select layer!' }
        (r = new ActionReference()).putName(s2t('action'), atn);
        r.putName(s2t('actionSet'), set);
        (d = new ActionDescriptor()).putReference(s2t('target'), r);
        try { executeAction(s2t('play'), d) } catch (e) { throw e + '\nCannot play action "' + atn + '" from set "' + set + '"' }
    }
} catch (e) { alert(e) }
 
  • Спасибо
Реакции: NatalieRedFox_333
Layers - auto crop
Код:
/**Кадрирование сканов по границам без использования CropAndStraighten
 * (без учета поворота изображения)
 * https://community.adobe.com/t5/photoshop-ecosystem/how-to-cut-out-photos/m-p/12222294
 */

#target photoshop
var lr = new AM('layer'),
    doc = new AM('document');
lr.copyToLayer(2)
lr.motionBlur(90, 2000)
lr.threshold(230)
var layerBounds = lr.descToObject(lr.getProperty('bounds')),
    objectBounds = {};
lr.selectOrdinalChannel()
lr.substractSelection(layerBounds.top, layerBounds.right / 2, layerBounds.bottom, layerBounds.right)
objectBounds.left = doc.hasProperty('selection') ? lr.descToObject(doc.getProperty('selection')).right : layerBounds.left
lr.selectOrdinalChannel()
lr.substractSelection(layerBounds.top, layerBounds.left, layerBounds.bottom, layerBounds.right / 2)
objectBounds.right = doc.hasProperty('selection') ?  lr.descToObject(doc.getProperty('selection')).left : layerBounds.right
lr.deselect()
lr.deleteOrdinalLayer()
lr.motionBlur(0, 2000)
lr.threshold(230)
lr.selectOrdinalChannel()
lr.substractSelection(layerBounds.bottom / 2, layerBounds.left, layerBounds.bottom, layerBounds.right)
objectBounds.top = doc.hasProperty('selection') ? lr.descToObject(doc.getProperty('selection')).bottom : layerBounds.top
lr.selectOrdinalChannel()
lr.substractSelection(layerBounds.top, layerBounds.left, layerBounds.bottom / 2, layerBounds.right)
objectBounds.bottom = doc.hasProperty('selection') ? lr.descToObject(doc.getProperty('selection')).top : layerBounds.bottom
lr.deleteOrdinalLayer()
lr.makeSelection(objectBounds)
lr.crop()
with (objectBounds) {
    lr.substractSelection((bottom - top) * 0.015, (right - left) * 0.015, bottom - top - (bottom - top) * 0.015, (right - left) - (right - left) * 0.015)
}
lr.contentFill()
lr.deselect()
function AM(target, order) {
    var s2t = stringIDToTypeID,
        t2s = typeIDToStringID;
    target = s2t(target)
    this.getProperty = function (property, descMode, 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'), order ? s2t(order) : s2t('targetEnum'));
        return descMode ? executeActionGet(r) : 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'), order ? s2t(order) : 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.copyToLayer = function (copies) {
        var i = copies ? copies : 1
        for (i = 0; i < copies; i++) executeAction(s2t('copyToLayer'), undefined, DialogModes.NO);
    }
    this.motionBlur = function (angle, distance) {
        (d = new ActionDescriptor()).putInteger(s2t('angle'), angle);
        d.putUnitDouble(s2t('distance'), s2t('pixelsUnit'), distance);
        executeAction(s2t('motionBlur'), 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);
    }
    this.threshold = function (level) {
        (d = new ActionDescriptor()).putInteger(s2t('level'), level);
        executeAction(s2t('thresholdClassEvent'), d, DialogModes.NO);
    }
    this.selectOrdinalChannel = function () {
        (r = new ActionReference()).putProperty(s2t('channel'), s2t('selection'));
        (d = new ActionDescriptor()).putReference(s2t('null'), r);
        (r1 = new ActionReference()).putEnumerated(s2t('channel'), s2t('ordinal'), s2t('targetEnum'));
        d.putReference(s2t('to'), r1);
        executeAction(s2t('set'), d, DialogModes.NO);
    }
    this.substractSelection = 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('subtractFrom'), d, DialogModes.NO);
    }
    this.makeSelection = function (bounds) {
        (r = new ActionReference()).putProperty(s2t('channel'), s2t('selection'));
        (d = new ActionDescriptor()).putReference(s2t('null'), r);
        (d1 = new ActionDescriptor()).putUnitDouble(s2t('top'), s2t('pixelsUnit'), bounds.top);
        d1.putUnitDouble(s2t('left'), s2t('pixelsUnit'), bounds.left);
        d1.putUnitDouble(s2t('bottom'), s2t('pixelsUnit'), bounds.bottom);
        d1.putUnitDouble(s2t('right'), s2t('pixelsUnit'), bounds.right);
        d.putObject(s2t('to'), s2t('rectangle'), d1);
        executeAction(s2t('set'), d, DialogModes.NO);
    }
    this.deleteOrdinalLayer = 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.crop = function () {
        try {
            (d = new ActionDescriptor()).putBoolean(s2t('delete'), true);
            executeAction(s2t('crop'), d, DialogModes.NO);
        } catch (e) { }
    }
    this.contentFill = function () {
        (d = new ActionDescriptor()).putEnumerated(s2t('cafSamplingRegion'), s2t('cafSamplingRegion'), s2t('cafSamplingRegionAuto'));
        d.putBoolean(s2t('cafSampleAllLayers'), false);
        d.putEnumerated(s2t('cafColorAdaptationLevel'), s2t('cafColorAdaptationLevel'), s2t('cafColorAdaptationDefault'));
        d.putEnumerated(s2t('cafRotationAmount'), s2t('cafRotationAmount'), s2t('cafRotationAmountNone'));
        d.putBoolean(s2t('cafScale'), false);
        d.putBoolean(s2t('cafMirror'), false);
        d.putEnumerated(s2t('cafOutput'), s2t('cafOutput'), s2t('cafOutputToCurrentLayer'));
        executeAction(s2t('cafWorkspace'), d, DialogModes.NO);
    }
    function getDescValue(d, p) {
        switch (d.getType(p)) {
            case DescValueType.OBJECTTYPE: return (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 [t2s(d.getEnumerationType(p)), t2s(d.getEnumerationValue(p))];
            default: break;
        };
    }
}
 
  • Спасибо
Реакции: NatalieRedFox_333
Files - shuffle files

Смешивание файлов из двух разных папок с сохранением общей нумерации файлов
(съемка постановочных кадров с подмешиванием фиксированного набора ранее снятых сюжетов)

Shuffle files
(скрипт в по ссылке, т.к. не влезает в ограничение на размер сообщения)

 
  • Спасибо
Реакции: NatalieRedFox_333
Сниппеты - это то что надо !!! Супер !!!
Спасибо что поделились...))) Во первых их можно куда нибудь прикрутить в случае необходимости, и второе можно адаптировать под свои нужды
или просто посмотреть что и как реализовано ...
В любом случае очень полезно....)))
Было бы вообще неплохо ее закрепить в разделе Photoshop :)
 
Последнее редактирование:
  • Спасибо
Реакции: jazzy
Да, это действительно полезная вещь - многие фрагменты можно переиспользовать в сходных задачах, либо собрать более сложный скрипт из отдельных кусков (когда попадается новая задача, часто просто беру что-то похожее в качестве шаблона и слегка переписываю). У меня их больше полутора сотен накопилось, так что постепенно буду выкладывать (это полезно в том числе и мне, так есть повод разобраться в бардаке - убрать повторы, объединить однотипные решения и т.п.)
 
Последнее редактирование:
Да, это действительно полезная вещь - многие фрагменты можно переиспользовать в сходных задачах, либо собрать более сложный скрипт из отдельных фрагментов. У меня их больше полутора сотен накопилось, так что постепенно буду выкладывать (это полезно в том числе и мне, так есть повод разобраться в бардаке - есть повторы, есть однотипные решения, которые можно объединить и т.п.)
Именно так !
 
Layers - change fill effect
Код:
/**Изменение эффекта заливки активного слоя
 * https://community.adobe.com/t5/photoshop-ecosystem/modify-a-layereffects-object/m-p/12196380
 * https://youtu.be/m45ATepUvRw
 */
#target photoshop
changeFill(255, 0, 0); // R, G, B values
function changeFill(red, green, blue) {
    s2t = stringIDToTypeID;
    (r = new ActionReference()).putProperty(s2t("property"), p = s2t("layerEffects"));
    r.putEnumerated(s2t("layer"), s2t("ordinal"), s2t("targetEnum"));
    var fx = executeActionGet(r).hasKey(p) ? executeActionGet(r).getObjectValue(p) : new ActionDescriptor(),
        currentFill = fx.hasKey(p = s2t("solidFill")) ? fx.getObjectValue(p) : new ActionDescriptor();
    if (fx.hasKey(p = s2t("solidFillMulti"))) fx.erase(p);
    (d = new ActionDescriptor()).putDouble(s2t("red"), red);
    d.putDouble(s2t("green"), green);
    d.putDouble(s2t("blue"), blue);
    currentFill.putObject(s2t("color"), s2t("RGBColor"), d);
    fx.putObject(s2t("solidFill"), s2t("solidFill"), currentFill);
    (d = new ActionDescriptor()).putReference(s2t("null"), r);
    d.putObject(s2t("to"), s2t("layerEffects"), fx);
    executeAction(s2t("set"), d, DialogModes.NO);
}
 
Layers - change gradient fill effect
Код:
/**Изменение эффекта градиентной заливки активного слоя
 * https://community.adobe.com/t5/photoshop-ecosystem/modify-a-layereffects-object/m-p/12196380#M567928
 * https://youtu.be/Ynj3IKhI5_k
 */
#target photoshop
changeFill([255, 0, 0], [0, 255, 0], [0, 0, 255]); // [R, G, B] values
function changeFill() {
    s2t = stringIDToTypeID;
    (r = new ActionReference()).putProperty(s2t('property'), p = s2t('layerEffects'));
    r.putEnumerated(s2t('layer'), s2t('ordinal'), s2t('targetEnum'));
    if (executeActionGet(r).hasKey(p)) var fx = executeActionGet(r).getObjectValue(p);
    if (fx && fx.hasKey(p = s2t('gradientFill'))) {
        var gradientFill = fx.getObjectValue(p),
            gradient = gradientFill.getObjectValue(s2t('gradient')),
            colors = gradient.getList(s2t('colors'));
        if (arguments.length == colors.count) {
            var newColors = new ActionList();
            for (var i = 0; i < colors.count; i++) {
                var currentColor = colors.getObjectValue(i);
                (d = new ActionDescriptor()).putDouble(s2t('red'), arguments[i][0]);
                d.putDouble(s2t('green'), arguments[i][1]);
                d.putDouble(s2t('blue'), arguments[i][2]);
                currentColor.putObject(s2t('color'), s2t('RGBColor'), d)
                newColors.putObject(s2t('colorStop'), currentColor)
            }
            gradient.putList(s2t('colors'), newColors)
            gradientFill.putObject(s2t('gradient'), s2t('gradientClassEvent'), gradient);
            fx.putObject(s2t('gradientFill'), s2t('gradientFill'), gradientFill);
            (d = new ActionDescriptor()).putReference(s2t('null'), r);
            d.putObject(s2t('to'), s2t('layerEffects'), fx);
            executeAction(s2t('set'), d, DialogModes.NO);
        }
    }
}