- PVSM.RU - https://www.pvsm.ru -

Играем в сапера в фотошопе

Играем в сапера в фотошопе - 1

По роду своей деятельности мне периодически приходится автоматизировать свою работу в фотошопе. Точнее я мог бы этого не делать, но природная лень не оставляет шансов в борьбе с рутиной, как говориться «лучше час потерять, зато потом за 5 минут долететь». Все бы наверное так и оставалось на уровне отдельных разрозненных скриптов если бы не пост [1] от enotus [2]. Благодаря ему я узнал, что к фотошопу (как впрочем и другим продуктам от Adobe) можно писать расширения на HTML+JS. И пошло, поехало.

Как-то так сложилось, что изучение всего нового я обычно начинаю с написания простенькой игрушки на этом самом новом. Для фотошопа я выбрал Сапера. В этом примере я бы хотел рассказать о создании интерфейса расширения, взаимодействии с фотошопом и обработкой событий. Так что кому все еще интересно, прошу подкат.

Как уже описано в статье [1] моего предшественника нам понадобится Brackets [3] с установленным расширением [4] ну и собственно сам Photoshop.

Также рекомендую сразу запастись документацией со страницы Adobe [5]: наиболее вероятно что потребуется Photoshop CC JavaScript Reference и возможно Photoshop CC Scripting Guide. Они не имеют прямого отношения к написанию расширений, но потребуются для взаимодействия с фотошопом.

Приступаем. Создадим проект в Brackets при помощи расширения:

Играем в сапера в фотошопе - 2

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

Играем в сапера в фотошопе - 3

В папку CSS я закинул дополнительно файл для светлой темы topcoat т.к. предпочитаю работать со светлым интерфейсом, но это не обязательно. Кстати о Topcoat, скачать и почитать документацию можно тут [6].

Manifest можно и не трогать, для последней версии фотошопа там все настроено. Если потребуется изменить иконку расширения или разрешить работу в фотошопе более ранней версии, уточните эти моменты в документации от производителя [7].

В папке js нас интересует main.js, собственно основной файл нашего проекта. Но прежде чем переходить к нему нам нужно создать интерфейс нашего расширения. Это делается с помощью HTML в файле index.html.

И остался еще один важный файл о котором я не упомянул: hostscript.jsx. JSX скрипт это как раз то что использовали уже довольно давно для автоматизации различных процессов в фотошопе и других проектах от Adobe. У них даже есть специальная утилитка ExtendScript Toolkit, она тоже может пригодится, например для отладки скриптов jsx.

index.html

В этом файле тоже уже все подготовлено, подключена темная тема 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>

В результате мы получим следующую картину:

Играем в сапера в фотошопе - 4

Можно переходить к JS.

main.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);

hostscript.jsx

Как я уже писал ранее, это основной способ взаимодействия с приложением, в данном случае с 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, чтобы мы увидели свое не подписанное расширение в фотошопе.

Играем в сапера в фотошопе - 5

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

Играем в сапера в фотошопе - 6

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

Играем в сапера в фотошопе - 7

Ну и видео процесса:

Напоследок скажу пару слов про подписывание ваших расширений.
Не забываейте удалять все скрытые файлы перед подписыванием.
В 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/