Часто нам бывает надо сделать что-то с пачкой картинок. Есть несколько способов добиться этого:
У Photoshop-а есть COM API, в котором покрыты многие из фотошоповских функций. Его, разумеется, можно использовать из JS- или VBS-скриптов. Adobe любезно предоставила разработчикам свою IDE, с автокопмлитом и брейкпоинтами. Поддерживаемые языки в ней JScript, VBScript (Win) и AppleScript (Mac). Я остановился на JScript, потому как большинству будет лучше всего понятен именно он.
Её зовут ExtendScript Toolkit. Вот она:
Что в ней меня поразило:
Приятно, что jsx можно компилировать (File → Export as binary), при этом будет создан файл с расширением jsxbin. Контент его будет примерно таким:
Итак, IDE на первый взгляд неудобная, но поработав в ней минут 30, привыкаешь.
Начинается он с фразы
Есть средства для работы с файловой системой, поддержка сокетов, reflection, XML. Класс Object есть.
Для подключения к Photoshop-у существует глобальный объект app, ActiveXObject делать не надо. Активный документ в нём – app.activeDocumet. Функция alert показывает сообщение в Photoshop-е.
При падении ошибки ничего не происходит, скрипт молча прекращает выполнение, как будто и не было его вовсе.
Понравилось, как измерения (px, pt, cm, mm) конвертируются друг в друга:
Задача: в папке есть 100 файлов. Надо внедрить в каждый из них лого, которое есть в PSD-файле.
Пример лого:
А вот и скрипт:
Пример того, что получится:
Полностью скрипт вылолжил на pastebin.
Самое вкусное, что есть в Photoshop-е – blending options! А их-то в API как раз и нет. Есть copyLayerStyle, но она работает некорректно даже из GUI (вы можете это проверить, поиграв с параметрами drop shadow). Поэтому лого, конечно, мы вставить можем, но результат будет не сильно превосходить тот же ImageMagick.
UPD: есть два способа быстро и легко применить стили из скрипта:
Почитав доки (вы можете найти их в %ProgramFiles%Adobe\Adobe Photoshop CS5\Scripting\Documents\), мы узнаём, что оказывается, Photoshop умеет записывать действия пользователя. Для этого надо:
Но, выполнив этот код, обнаружим, что он работает.
Можно найти, что executeAction может как показать диалог пользователю, так и сделать свою работу молча (это определяет последний параметр). Сами ID-шники нигде не описаны, о них (как и о том, что будет с ними в CS6) мы можем только гадать.
Тем не менее, фича логгирования действий довольно интересная, если очень надо, можно по-быстрому накидать скриптик для себя.
Заодно я написал вот такие функции:
API вкусное, очень вкусное. Но отсутствие поддержки blending options сильно удручает; если они нужны – будьте готовы к тому, что придётся возиться со страшным кодом. Если всё, что вам надо (что как раз и надо в большинстве случаев от пакетной обработки) – обвести картинку рамочкой, думаю, ImageMagick в этом случае будет быстрее и намного удобнее.
фильтры, гистограммы
RAW
color profiles, как в Photoshop-е
javascript – удобный, понятный почти всем язык
документация с примерами
отсутствие blending options
для работы нужен Photoshop /* внезапно */
работает довольно медленно
Adobe Photoshop Scripting – официальный ресурс
Scripting Photoshop – небольшой, но полезный тьюториал по скрпитингу в Photoshop
PS-Scripts – форум о скриптах для Photoshop
В качестве упражнения предлагается скрипт, который может действительно пригодиться: сделать так, чтобы лого на фотке добавлялось в той же цветовой гамме, что и фотка – например, синее или жёлтое для сине-жёлтой фотки: это сделает лого не портящим общий цвет и настроение фотки. Лого не должно сливаться с цветом, т.е. не быть синим на синем. Кроме того, будет классно, если лого не будет на поверхности вроде травы, его можно попробовать перенести в другой угол или перекрасить.
- используя ImageMagick – очень удобная консольная утилита, много чего умеющая
- на The GIMP – там есть Scheme (диалект lisp-а) и Python
- штатными средствами: PHP+gd / Powershell+System.Drawing / Python + PIL
- в photoshop-е на JScript, VBScript или AppleScript
Нам понадобится
- Adobe Photoshop CS5 (можно CS4)
- Adobe ExtendScript Toolkit (входит в дистрибутив Photoshop)
- Знание JScript
- Несколько фоток
Теория
У Photoshop-а есть COM API, в котором покрыты многие из фотошоповских функций. Его, разумеется, можно использовать из JS- или VBS-скриптов. Adobe любезно предоставила разработчикам свою IDE, с автокопмлитом и брейкпоинтами. Поддерживаемые языки в ней JScript, VBScript (Win) и AppleScript (Mac). Я остановился на JScript, потому как большинству будет лучше всего понятен именно он.
IDE
Её зовут ExtendScript Toolkit. Вот она:
Что в ней меня поразило:
- по умолчанию установлен не моноширинный шрифт, а какая-то дрянь. Тут же пофиксил
- нет watch-ей. За это казнить надо. Их роль выполняет data browser и javascript console
- по привычке нажал ctrl-D (копирует строчку в Решарпере) – и о чудо, оно работает!
- там есть профайлинг
- хелп есть, по контенту сносный, но до уровня msdn недотягивает.
Приятно, что jsx можно компилировать (File → Export as binary), при этом будет создан файл с расширением jsxbin. Контент его будет примерно таким:
@JSXBIN@ES@2.0@MyBbyBnAIMVbyBn0AHJWn
Удобно, особенно если надо написать скрипт для фотошопа под заказ и не хочется давать исходники. Насчёт возможности его декомпиляции я детально не разбирался, но думаю, что переменные он меняет и кое-какую оптимизацию всё-таки делает. Итак, IDE на первый взгляд неудобная, но поработав в ней минут 30, привыкаешь.
Скриптовый язык
Начинается он с фразы
#target photoshop
Это обычный javascript с библиотеками Adobe. Есть средства для работы с файловой системой, поддержка сокетов, reflection, XML. Класс Object есть.
Для подключения к Photoshop-у существует глобальный объект app, ActiveXObject делать не надо. Активный документ в нём – app.activeDocumet. Функция alert показывает сообщение в Photoshop-е.
При падении ошибки ничего не происходит, скрипт молча прекращает выполнение, как будто и не было его вовсе.
Понравилось, как измерения (px, pt, cm, mm) конвертируются друг в друга:
app.activeDocument.width.as("px");
Т.к. ExtendScript кроссплатформенный, пути к файлам представляются как /d/Temp/…Живой пример
Задача: в папке есть 100 файлов. Надо внедрить в каждый из них лого, которое есть в PSD-файле.
Пример лого:
А вот и скрипт:
#target photoshop
app.bringToFront(); // запускаем Photoshop. Если он уже запущен, подключимся именно к нему, не к новому инстансу.
var Constants = { /* определим кое-какие константы */ }
ProcessDir(Constants.InputDir, Constants.OutputDir);
function ProcessDir(dir, outDir) {
var folder = Folder(dir); // Adobe-овский объект
var files = folder.getFiles(Constants.FileMask); // Внимание, две маски через запятую (*.jpg,*.png) уже не работают.
var outFolder = Folder(outDir);
if (!outFolder.exists) {
if (!outFolder.create()) {
alert("Cannot create output folder");
return; // может и не получиться
}
}
var totalFiles = 0;
for (var fileNum in files) {
var outFile = GetOutputFileName(files[fileNum], outFolder.fullName); // куда писать результат
AddLogoToFile(files[fileNum], outFile); // собственно, сама обработка
totalFiles++;
}
alert(totalFiles + " files processed"); // увидит юзер в Photoshop-е в конце обработки
}
function AddLogoToFile(file, outputFile) {
var photoFile = File(file); // Так открываются файлы, строчку open не понимает
var logoFile = File(Constants.AddLogo.LogoPath);
app.open(logoFile); // открываем лого
app.activeDocument.artLayers["Text"].copy(); // ArtLayers – слои в файле. Этот слой назывался "Text"
var logoWidth = app.activeDocument.width.as("px");
var logoHeight = app.activeDocument.height.as("px");
app.activeDocument.close();
app.open(photoFile); // открываем фотку
var width = app.activeDocument.width.as("px");
var height = app.activeDocument.height.as("px");
var logoLayer = app.activeDocument.artLayers.add(); // добавляем на фотку новый слой, куда поместим лого
logoLayer.name = "Logo"; // название нового слоя
app.activeDocument.paste(); // вставляем лого из clipboard
var shape = [ // Photoshop вставляет всё в середину; выделяем лого, чтобы перенести его
[(width - logoWidth) / 2, (height - logoHeight) / 2],
[(width - logoWidth) / 2, (height + logoHeight) / 2],
[(width + logoWidth) / 2, (height + logoHeight) / 2],
[(width + logoWidth) / 2, (height - logoHeight) / 2]
];
app.activeDocument.selection.select(shape);
app.activeDocument.selection.translate( // переносим selection вправо вниз
new UnitValue((width - logoWidth)/ 2, "px"),
new UnitValue((height - logoHeight) / 2, "px"));
var minImageDimension = Math.min(width, height); // масштабируем лого, чтобы оно было в 5 раз меньше минимального размера фотки
var logoScaleMultiplier = minImageDimension / 5 / logoWidth * 100;
app.activeDocument.selection.resize(logoScaleMultiplier, logoScaleMultiplier, AnchorPosition.BOTTOMRIGHT); // обратите внимание на последний аргумент
app.activeDocument.selection.deselect();
app.activeDocument.artLayers["Logo"].opacity = 75; // делаем слой полупрозрачным
app.activeDocument.artLayers["Logo"].blendMode = BlendMode.LUMINOSITY; // устанавливаем режим смешивания, чтобы выглядело симпатичнее
// а вот тут бы установить blending options! Об этом читайте дальше.
SaveFile(outputFile); // сохранит и закроет файл
}
function SaveFile(outputFile) {
var isPng = /png$/i.test(outputFile);
var saveOptions;
if (isPng) {
saveOptions = new PNGSaveOptions();
} else {
saveOptions = new JPEGSaveOptions(); /* неинтересный код про качество картинки */
}
app.activeDocument.saveAs(File(outputFile), saveOptions, true, Extension.LOWERCASE)
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES); // закрываем документ
}
Скрипт готов. Осталось сделать лого в формате PSD – такое, чтобы внутри был слой Text, на котором и должно быть размещено лого.Пример того, что получится:
Полностью скрипт вылолжил на pastebin.
О грустном
Самое вкусное, что есть в Photoshop-е – blending options! А их-то в API как раз и нет. Есть copyLayerStyle, но она работает некорректно даже из GUI (вы можете это проверить, поиграв с параметрами drop shadow). Поэтому лого, конечно, мы вставить можем, но результат будет не сильно превосходить тот же ImageMagick.
UPD: есть два способа быстро и легко применить стили из скрипта:
- записав Action с этими настройками и выполнив его (спасибо за подсказку serge2)
- сохранить стиль в preset-ах (используя кнопку «New Style» в диалоге «Blending Options»)
Немного о недокументированном API
Почитав доки (вы можете найти их в %ProgramFiles%Adobe\Adobe Photoshop CS5\Scripting\Documents\), мы узнаём, что оказывается, Photoshop умеет записывать действия пользователя. Для этого надо:
- Скопировать файл «ScriptListener.8li» из %ProgramFiles%Adobe\Adobe Photoshop CS5\Scripting\Utilities\ в %ProgramFiles%Adobe\Adobe Photoshop CS5\Plug-ins\Automate\
- (пере)запустить Photoshop
- Сделать то действие, о котором хочется узнать
- Найти на рабочем столе файлы ScriptListener.jsx и ScriptListener.vbs
- Не забыть удалить ScriptListener.8li (он тормозит работу Photoshop)
var idsetd = charIDToTypeID( "setd" );
var desc15 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref6 = new ActionReference();
var idPrpr = charIDToTypeID( "Prpr" );
var idLefx = charIDToTypeID( "Lefx" );
ref6.putProperty( idPrpr, idLefx );
var idLyr = charIDToTypeID( "Lyr " );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
ref6.putEnumerated( idLyr, idOrdn, idTrgt );
desc15.putReference( idnull, ref6 );
var idT = charIDToTypeID( "T " );
var desc16 = new ActionDescriptor();
var idScl = charIDToTypeID( "Scl " );
var idPrc = charIDToTypeID( "#Prc" );
desc16.putUnitDouble( idScl, idPrc, 100.000000 );
var idDrSh = charIDToTypeID( "DrSh" );
var desc17 = new ActionDescriptor();
var idenab = charIDToTypeID( "enab" );
desc17.putBoolean( idenab, true );
var idMd = charIDToTypeID( "Md " );
var idBlnM = charIDToTypeID( "BlnM" );
var idMltp = charIDToTypeID( "Mltp" );
desc17.putEnumerated( idMd, idBlnM, idMltp );
var idClr = charIDToTypeID( "Clr " );
var desc18 = new ActionDescriptor();
var idRd = charIDToTypeID( "Rd " );
desc18.putDouble( idRd, 0.000000 );
var idGrn = charIDToTypeID( "Grn " );
desc18.putDouble( idGrn, 0.000000 );
var idBl = charIDToTypeID( "Bl " );
desc18.putDouble( idBl, 0.000000 );
var idRGBC = charIDToTypeID( "RGBC" );
desc17.putObject( idClr, idRGBC, desc18 );
var idOpct = charIDToTypeID( "Opct" );
var idPrc = charIDToTypeID( "#Prc" );
desc17.putUnitDouble( idOpct, idPrc, 75.000000 );
var iduglg = charIDToTypeID( "uglg" );
desc17.putBoolean( iduglg, true );
var idlagl = charIDToTypeID( "lagl" );
var idAng = charIDToTypeID( "#Ang" );
desc17.putUnitDouble( idlagl, idAng, 120.000000 );
var idDstn = charIDToTypeID( "Dstn" );
var idPxl = charIDToTypeID( "#Pxl" );
desc17.putUnitDouble( idDstn, idPxl, 5.000000 );
var idCkmt = charIDToTypeID( "Ckmt" );
var idPxl = charIDToTypeID( "#Pxl" );
desc17.putUnitDouble( idCkmt, idPxl, 0.000000 );
var idblur = charIDToTypeID( "blur" );
var idPxl = charIDToTypeID( "#Pxl" );
desc17.putUnitDouble( idblur, idPxl, 5.000000 );
var idNose = charIDToTypeID( "Nose" );
var idPrc = charIDToTypeID( "#Prc" );
desc17.putUnitDouble( idNose, idPrc, 0.000000 );
var idAntA = charIDToTypeID( "AntA" );
desc17.putBoolean( idAntA, false );
var idTrnS = charIDToTypeID( "TrnS" );
var desc19 = new ActionDescriptor();
var idNm = charIDToTypeID( "Nm " );
desc19.putString( idNm, "Linear" );
var idShpC = charIDToTypeID( "ShpC" );
desc17.putObject( idTrnS, idShpC, desc19 );
var idlayerConceals = stringIDToTypeID( "layerConceals" );
desc17.putBoolean( idlayerConceals, true );
var idDrSh = charIDToTypeID( "DrSh" );
desc16.putObject( idDrSh, idDrSh, desc17 );
var idLefx = charIDToTypeID( "Lefx" );
desc15.putObject( idT, idLefx, desc16 );
executeAction( idsetd, desc15, DialogModes.NO );
Как вы думаете, что делает этот код? Он добавляет тень (Drop Shadow) к слою, это видно по название «DrSh». Я подозреваю, что внутри Photoshop-а прямо так и называются контролы в GUI.Но, выполнив этот код, обнаружим, что он работает.
Можно найти, что executeAction может как показать диалог пользователю, так и сделать свою работу молча (это определяет последний параметр). Сами ID-шники нигде не описаны, о них (как и о том, что будет с ними в CS6) мы можем только гадать.
Тем не менее, фича логгирования действий довольно интересная, если очень надо, можно по-быстрому накидать скриптик для себя.
Ещё скрипты
Заодно я написал вот такие функции:
- ресайзинг картинок до определённого размера (ширина не больше X, высота не больше Y)
- добавление рамок к картинкам – таких же, как в этом топике
Интересные факты
- в API есть поддержка RAW. После того, как вы обработали RAW-файлы в Photoshop-е, сохранив в них настройки, вы можете быстро сконвертировать их в JPEG
- в отличие от blending options, фильтры представлены в API довольно хорошо, для каждого из них есть функция
- код в jsx-файлах можно вешать на события в Photoshop: например, при открытии файла добавлять в него новый слой, и так далее
- API есть и для Illustrator, и для Bridge
- из API есть доступ к гистограмме и к каналам
Выводы
API вкусное, очень вкусное. Но отсутствие поддержки blending options сильно удручает; если они нужны – будьте готовы к тому, что придётся возиться со страшным кодом. Если всё, что вам надо (что как раз и надо в большинстве случаев от пакетной обработки) – обвести картинку рамочкой, думаю, ImageMagick в этом случае будет быстрее и намного удобнее.
+ / -
фильтры, гистограммы
RAW
color profiles, как в Photoshop-е
javascript – удобный, понятный почти всем язык
документация с примерами
отсутствие blending options
для работы нужен Photoshop /* внезапно */
работает довольно медленно
Почитать
Adobe Photoshop Scripting – официальный ресурс
Scripting Photoshop – небольшой, но полезный тьюториал по скрпитингу в Photoshop
PS-Scripts – форум о скриптах для Photoshop
Подумать
В качестве упражнения предлагается скрипт, который может действительно пригодиться: сделать так, чтобы лого на фотке добавлялось в той же цветовой гамме, что и фотка – например, синее или жёлтое для сине-жёлтой фотки: это сделает лого не портящим общий цвет и настроение фотки. Лого не должно сливаться с цветом, т.е. не быть синим на синем. Кроме того, будет классно, если лого не будет на поверхности вроде травы, его можно попробовать перенести в другой угол или перекрасить.