- PVSM.RU - https://www.pvsm.ru -
По роду своей деятельности мне периодически приходится автоматизировать свою работу в фотошопе. Точнее я мог бы этого не делать, но природная лень не оставляет шансов в борьбе с рутиной, как говориться «лучше час потерять, зато потом за 5 минут долететь». Все бы наверное так и оставалось на уровне отдельных разрозненных скриптов если бы не пост [1] от enotus [2]. Благодаря ему я узнал, что к фотошопу (как впрочем и другим продуктам от Adobe) можно писать расширения на HTML+JS. И пошло, поехало.
Как-то так сложилось, что изучение всего нового я обычно начинаю с написания простенькой игрушки на этом самом новом. Для фотошопа я выбрал Сапера. В этом примере я бы хотел рассказать о создании интерфейса расширения, взаимодействии с фотошопом и обработкой событий. Так что кому все еще интересно, прошу подкат.
Как уже описано в статье [1] моего предшественника нам понадобится Brackets [3] с установленным расширением [4] ну и собственно сам Photoshop.
Также рекомендую сразу запастись документацией со страницы Adobe [5]: наиболее вероятно что потребуется Photoshop CC JavaScript Reference и возможно Photoshop CC Scripting Guide. Они не имеют прямого отношения к написанию расширений, но потребуются для взаимодействия с фотошопом.
Приступаем. Создадим проект в Brackets при помощи расширения:

У нас появится папка с полным набором файлов для работы расширения:

В папку CSS я закинул дополнительно файл для светлой темы topcoat т.к. предпочитаю работать со светлым интерфейсом, но это не обязательно. Кстати о Topcoat, скачать и почитать документацию можно тут [6].
Manifest можно и не трогать, для последней версии фотошопа там все настроено. Если потребуется изменить иконку расширения или разрешить работу в фотошопе более ранней версии, уточните эти моменты в документации от производителя [7].
В папке js нас интересует main.js, собственно основной файл нашего проекта. Но прежде чем переходить к нему нам нужно создать интерфейс нашего расширения. Это делается с помощью HTML в файле index.html.
И остался еще один важный файл о котором я не упомянул: hostscript.jsx. JSX скрипт это как раз то что использовали уже довольно давно для автоматизации различных процессов в фотошопе и других проектах от Adobe. У них даже есть специальная утилитка ExtendScript Toolkit, она тоже может пригодится, например для отладки скриптов jsx.
В этом файле тоже уже все подготовлено, подключена темная тема topcoat, добавлены все необходимые скрипты. Собственно все что требуется, это заполнить div#content:
<div id="content">
<div>
Поле:<br>
<input type="text" id="w" class="topcoat-text-input" placeholder="width" value="6"> x <input type="text" id="h" class="topcoat-text-input" placeholder="height" value="6"><br>
Количество мин:<br>
<input type="text" id="m" class="topcoat-text-input" placeholder="width" value="8"><br>
<button id="btn_start" class="topcoat-button--large hostFontSize">New game</button>
</div>
<hr>
<div>
<h2>Осталось открыть: <span id="res">-</span></h2>
<h3><span id="timer">0</span> sec.</h3>
<button id="btn_check" class="topcoat-button--large hostFontSize" disabled>Check cell</button>
</div>
<div>
<button id="btn_mark" class="topcoat-button--large hostFontSize" disabled>Mark</button> <button id="btn_unmark" class="topcoat-button--large hostFontSize" disabled>UnMark</button>
</div>
</div>
В результате мы получим следующую картину:

Можно переходить к JS.
По сути тут уже есть пример самого простого использования:
/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */
/*global $, window, location, CSInterface, SystemPath, themeManager*/
(function () {
'use strict';
var csInterface = new CSInterface();
function init() {
themeManager.init();
$("#btn_test").click(function () {
csInterface.evalScript('sayHello()');
});
}
init();
}());
Самая главная тут строчка это csInterface.evalScript('sayHello()');, именно таким образом происходит взаимодействие с приложением. Дело в том, что механизм расширений универсальный и не зависит от приложения в котором выполняется, но по этой же причине ни чего не знает от функционале — ему все равно фотошоп это иллюстратор. Именно для этого и используются скрипты JSX. А sayHello() лишь один из методов реализованных в hostscript.jsx.
Но, нам же нужно не только передавать данные в jsx, но и получать результаты. Поэтому можно использовать callBack функцию, в том числе анонимную:
csInterface.evalScript('checkCell()', function(result){
if(result<=0)
{
stopGame();
}
else
{
$("#res").html(result); //вывод количества закрытых клеток без мин
}
});
Вот примерно так я проверяю клетки на мины: result содержит количество клеток которые еще нужно открыть для победы. Мина соответствует -1. Если вы обратили внимание среди скриптов сразу подключен jQuery. Так что взаимодействовать с интерфейсом проще простого.
Единственный момент требующий отдельного упоминания в рамках main.js это реакция на события происходящие в приложении за пределами нашего расширения. С одной стороны тут все просто, с другой хуже чем хотелось бы. Первым делом с попытался поймать клик мышкой в документе — не тут то было, такого события просто нет. Как выяснилось позже события относятся скорее ко всему приложению, а не к документу и имеют характер «использован фильтр», «отменено последнее действие» и тому подобное. Помимо этого у всех событий есть 4-х буквенный идентификатор и длинный цифровой и в нашем случае нужен именно длинный цифровой, а в документации указаны лишь короткие коды. Благо у каждого приложения есть метод конвертации одного идентификатор в другой, но он работает в рамках jsx-скрипта.
В общем я не буду сейчас вас грузить деталями, воспользуемся примером из документации:
function registerPhotoshopEvent(in_eventId) {
var event = new CSEvent("com.adobe.PhotoshopRegisterEvent", "APPLICATION");
event.extensionId = csInterface.getExtensionID();
event.appId = csInterface.getApplicationID();
event.data = in_eventId
csInterface.dispatchEvent(event);
}
csInterface.addEventListener("PhotoshopCallback" , function(event) {
csInterface.evalScript("return (app.documents.length > 0)", stopGame)
});
var closeEventid = "1131180832"; //идентификатор события закрытия документа
registerPhotoshopEvent(closeEventid);
Как я уже писал ранее, это основной способ взаимодействия с приложением, в данном случае с Photoshop CC. Вот тут то и пригодится скаченная в самом начале Photoshop CC JavaScript Reference. Там содержится описание свойств и методов различных объектов фотошопа.
Я приведу здесь лишь пару примеров. Начнем с создания документа:
var startRulerUnits = app.preferences.rulerUnits; // сохраняем текущие единицы измерения
preferences.rulerUnits = Units.PIXELS; //Заменяем единицы измерения на пикселы
//создаем документ
mainDoc = app.documents.add(width, height, dpi, "Game", NewDocumentMode.RGB);
Вы прекрасно знаете, что размеры в фотошопе можно задавать в разных системах измерения. Соответственно и результат измерения вы будете получать в тех-же единицах. Поэтому за этим нужно внимательно следить. Чтобы не получить неприятный сюрприз лучше подстраховаться и перевести единицы измерения к нужному значению.
Обращаю внимание, что есть исключения. Например, выделенная область всегда задается и измеряется только в PIXELS. А например все векторные объекты меряются исключительно в POINTS.
Для конвертации единиц измерения я использовал небольшой метод подсмотренный где-то на просторах интернета:
function convertValue(value, from, to)
{
output = new UnitValue(value, from);
return output.as(to);
}
Но и тут вас ждет сюрприз. Конвертация из мм и дюймов в пикселы всегда происходит из расчета 72 точки на дюйм и если у вас другое разрешение изображения, следует произвести пересчет самостоятельно.
Ну и пожалуй последний пример. Очень многие вещи которые мы привыкли в фотошопе делать как одно действие программно нужно реализовывать множеством команд. Например для склеивания слоев разными способами (склеить все, только видимые, связанные) нужно реализовывать самостоятельно. Из доступных есть только вариант склеить с предыдущим слоем, а дальше уже ваше дело (проверять видимость, связанность, менять местами слои). Похожая ситуация с выделением области. Вы не можете выделить круг или столбец в один пиксел. Все что вам предлагают засунуть массив координат точек, а уж какая это будет фигура лишь от вас зависит:
function DrawBomb(layer, x, y, colorHEX)
{
var startRulerUnits = app.preferences.rulerUnits; // сохраняем единицы измерения
preferences.rulerUnits = Units.PIXELS; //Заменяем единицы измерения на пикселы
mainDoc.activeLayer = layer;
mainDoc.selection.select([
[(x+0.2)*cellSize+padding, (y+0.2)*cellSize+padding],
[(x+0.8)*cellSize+padding, (y+0.2)*cellSize+padding],
[(x+0.8)*cellSize+padding, (y+0.8)*cellSize+padding],
[(x+0.2)*cellSize+padding, (y+0.8)*cellSize+padding],
[(x+0.2)*cellSize+padding, (y+0.2)*cellSize+padding]
]);
var color = new SolidColor;
color.rgb.hexValue = colorHEX;
mainDoc.selection.fill(color);
mainDoc.selection.deselect();
preferences.rulerUnits = startRulerUnits; //восстанавливаем единицы измерения
}
Ну вот, интерфейс нарисован, обработчики в js написаны, логика в jsx реализована, можно и поиграть.
Не забываем включить Debug Mode, чтобы мы увидели свое не подписанное расширение в фотошопе.

Теперь можно запускать Photoshop и активировать наше расширение:

Ну теперь можно и поиграть. Правда из-за того, что клики по картинке не отлавливаются, нужно двигать красный прямоугольник и нажимать кнопки действий на панели расширения.

Ну и видео процесса:
Напоследок скажу пару слов про подписывание ваших расширений.
Не забываейте удалять все скрытые файлы перед подписыванием.
В Windows путь к папке с расширением нужно начинать с точки, примерно так:
ZXPSignCmd.exe -sign ./ru.mobak.habrasample ru.mobak.habrasample.zxp cert.p12 pass
И прошу, не судите строго мой код, все таки я не программист.
Исходники расширения можно скачать тут [8].
Готовое расширение которое можно установить через Adobe Extension Manager лежит тут [9]. Подписано самодельным сертификатом, так что матюкается маленько.
P.S. Вспомнил, что забыл написать про один отличный ресурс, правда на английском, но с кучей примеров: http://www.davidebarranca.com/ [10]
Автор: Petrovich_Z
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/86135
Ссылки в тексте:
[1] пост: http://habrahabr.ru/post/221863/
[2] enotus: http://habrahabr.ru/users/enotus/
[3] Brackets: http://brackets.io/
[4] расширением: http://davidderaedt.github.io/CC-Extension-Builder-for-Brackets/
[5] документацией со страницы Adobe: http://www.adobe.com/devnet/photoshop/scripting.html
[6] тут: http://topcoat.io/topcoat/
[7] документации от производителя: http://wwwimages.adobe.com/www.adobe.com/content/dam/Adobe/en/devnet/cs-extension-builder/pdfs/CC_Extension_SDK.pdf
[8] скачать тут: https://www.dropbox.com/s/cfcj5wf7bkrf26r/ru.mobak.habrasample.zip?dl=0
[9] лежит тут: https://www.dropbox.com/s/p2jovdlfpiq2aq7/ru.mobak.habrasample.zxp?dl=0
[10] http://www.davidebarranca.com/: http://www.davidebarranca.com/
[11] Источник: http://habrahabr.ru/post/253337/
Нажмите здесь для печати.