Create a Calendar Using Scripting in Photoshop

Создание календаря, используя скрипт

Создание графического календаря вручную, нелегкая задача. Именно поэтому лучше найти способ автоматизировать процесс. В этом уроке будет показано, как в Фотошопе сгенерировать календарь на весь год, используя JavaScript. Если вы знаетесь в программировании - это замечательно, но если нет — не волнуйтесь, описание довольно прозрачное (единственно, очень внимательно записывайте команды, в некоторых местах регистр также имеет значение):

Предварительный просмотр:
http://s5.uploads.ru/t/Rof08.jpg

(внизу каждого шага я буду предоставлять, сделанный по этому уроку, свой вариант кода — участок для данного шага — который с грехом пополам получился. В скобках мои примечания.)
________________________________

Шаг 1
Согласно Adobe, скрипт - это ряд команд, которые задают Фотошопу выполнить одно или более действий. Список свойств и методов, поддерживаемых Фотошопом CS4, с примерами, можно посмотреть здесь - это даст общее представление относительно того, как выглядят и за что отвечают команды используемые в скрипте (при желании, после прочтения темы, это руководство может помочь решить свои задумки по автоматизации).
________________________________

Шаг 2
Давайте приступим. Главная идея состоит в том, чтобы создать текстовые слои на каждый месяц, содержащие даты. Поэтому главным образом мы будем работать вокруг генерации текста. Запустите ExtendScript Toolkit (идет в комплекте с Фш) и создайте новый файл JavaScript. Он будет содержать все команды, которые мы собираемся задать для Фотошопа. Если вы не хотите использовать ExtendScript Toolkit, то можете работать в любом текстовом редакторе.

________________________________

Шаг 3
Сначала мы определяем переменные значения, которые будут использованы при создании документа и систему цветов календаря. Я задал размеры 1280 на 800рх, при разрешении в 72 pixels/inch, с именем "PhotoshopScriptCalendar" и выбрал 2010-й как год календаря, который мы создаем:
http://s4.uploads.ru/t/ZC9Nd.png

Рассмотрим цвета, которые мы задали и к чему они будут впоследствии применены. "NormalColor" — для названий месяцев, будних чисел и дней. Цвет столбика воскресных дней будет зависеть от строчки "highlightColor", а "backColor" — это соответственно фоновый цвет календаря:

http://s5.uploads.ru/t/IOW9x.png

Код:
//параметры документа
width = 1280;
height = 800;
resolution = 72;
docName = "PhotoshopScriptCalendar";
year = 2010;

//Cистема цветов
normalColor = new SolidColor();
normalColor.rgb.hexValue = "FFFFFF";
highlightColor = new SolidColor();
highlightColor.rgb.hexValue = "A4B1D1";
backColor = new SolidColor();
backColor.rgb.hexValue = "1B4392";

Шаг 4
Поскольку мы главным образом собираемся работать с текстом, то следует определить для него некоторые переменные, которые мы собираемся неоднократно использовать в заголовках месяцев (название месяцев и дней можно писать кириллицей):

http://s5.uploads.ru/t/Y6v3B.png

Чтобы получить цвет колонки с воскресеньем отличным от остальных дней, нам нужно сделать его отдельным текстовым слоем. Поэтому мы зададим в коде два заголовка: "monthHeader" - от понедельника до субботы (пробелы будут учитываться как расстояние), и "sundayHeader" - воскресение. Каждая из этих двух переменных заканчивается двумя "\r".Они поддерживают перенос текста на новую строку, как нажатие на клавишу ENTER. Затем определим переменную сдвига первого числа года на нужный день - "firstIdent". Так как первое января было в пятницу, то мы должны учесть предыдущие ячейки дней и сделать необходимое число пробелов, которое будет зависеть от используемого шрифта и его размера, что проверяется опытным путем нескольких проб. Наконец, запишите все названия месяцев в порядке их следования:
http://s5.uploads.ru/t/tf80q.png

Код:
//Определение переменных для текстовых слоев
monthHeader = "Пн   Вт    Ср   Чт    Пт    Сб\r\r";
sundayHeader = "Вс\r\r"; 
firstIndent = "          ";  
months = new Array("Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь");

Шаг 5
Теперь, когда у нас есть все необходимые объекты, мы можем начать создавать .psd документ:
http://s5.uploads.ru/t/RdAQT.png

Как вы видите, код довольно понятен и удобочитаемый. Здесь задана команда создания нового документа с посыланием на значения ширины, высоты, разрешения и названия, которые были внесены ранее в 3-м шаге (посылание в скобках), также указывается цветовое пространство. По умолчанию все новые документы создаются в цветовом пространстве RGB, поэтому можно было опустить этот параметр, но если вы хотите изначально создать другое пространство, например CMYK, то так и записываете в отведенном месте "NewDocumentMode. CMYK". То же и с другими цветовыми моделями, просто записываете их название после точки: LAB, GRAYSCALE, BITMAP и т.д.

Далее мы записывает команду выделения всего документа (думаю понятно, что этой команде соответствует строка "doc.selection.selectAll()"), команду заливки и снятия выделения:

Код:
//Создание нового документа
doc = app.documents.add(width, height, resolution,
docName, NewDocumentMode.RGB);
doc.selection.selectAll();
doc.selection.fill(backColor);
doc.selection.deselect();

________________________________

Шаг 6
Затем мы прописываем добавление начального фона из стороннего изображения. Градиентный фон, что виден на окончательном результате, по сути является отдельным изображением. Для этого мы собираемся использовать функцию "openDialog ()" - она запускает диалоговое окно File > Open, что позволит выбрать изображение с жесткого диска:

http://s5.uploads.ru/t/qkVzm.png

Ниже прописываем следующую строчку. Мы собираемся использовать только первое выделенное изображение (вы ведь знаете, что в фотошопе можно разом открыть несколько пикчей — а в данном случае нужно всего одно, но ведь можно случайно выбрать и два), поэтому для множества "file" вписываем 0. Но также мы должны поставить проверку на то, было ли вообще выбрано какое-либо изображение "if":

http://s4.uploads.ru/t/7mICn.png

Затем изображение загружается и его окно становится активным, что мы прописываем "app.activeDocument":

http://s4.uploads.ru/t/0gK15.png

Изменяем размеры изображения к нашим параметрам. Снова прописываем выделение всего документа, потом копируем выделенную область и закрываем документ сохранения изменений (полагаю, что смысл прописанных внутри операций виден и понятен фотошоперам невооруженным глазом — ведь что такое "selectAll", "fill" и прочие названия знакомо всем):

http://s4.uploads.ru/t/Bv0ZW.png

Наконец, запишем команду вставки (ясно, что после закрытия, активным автоматически становится предыдущий документ), что приведет к помещению скопированного изображения на слой выше фонового. Заодно зададим слою название, например "BackgroundImage".

Заметьте, что если при выборе изображения нажать Cancel, то все действия этого шага, после первой строчки, не выполняются (потому что мы прописали команды только при варианте наличия выбора изображения, помните "if". Тобишь, если не нужна заморочка со вставкой картинки, ведь ее можно и потом добавить, то можно вообще не делать этот шаг):
http://s5.uploads.ru/t/Pulsm.png

Код:
//выбор фонового изображения
file = app.openDialog();

if(file[0]){
app.load(file[0]);
backFile = app.activeDocument;
backFile.resizeImage(width, height);
backFile.selection.selectAll();
backFile.selection.copy();
backFile.close(SaveOptions.DONOTSAVECHANGES);
doc.paste();
doc.layers[0].name = "Фоновое изображение";
}

________________________________

Шаг 7
Хорошо, приступим к самой генерации календаря (пишем на строчку ниже). Данный этап может показаться немного сложным, но мы будем продвигаться вперед шаг за шагом, и, надеюсь он немного прояснится.

Необходимо сделать набор действий, участвующих в создании каждого месяца (основу повторения действий). Используем петлю назначения "for", в которой переменная "curr" используется, чтобы обозначить текущий месяц, с которым мы работаем. Начальное значение для "curr" устанавливаем на 0, а конечное на 12 - это значит, что набор написанных позже действий будет прокручиваться 12 раз, в количестве месяцев года. Это-то нам и нужно (тем, кто хоть краем мозга знакомился с программирование, логика построения строчки должна быть понятной):

http://s5.uploads.ru/t/pUTWE.png

Сначала мы должны определить две переменные, которые собираемся использовать, чтобы расположить наши месяцы на холсте в виде сетки. Ими будут X и Y - координаты осей горизонтали и вертикали. При данных размерах холста разумно разместить на один ряд 4 месяца, таким образом для параметра смещения X, отвечающего за горизонталь, мы собираемся использовать операцию "%". В результате деления переменной "curr" на 4, получаем три ряда. Для оси Y мы используем функцию javascript - "Math.floor ()" (тут я не совсем въехал в смысл описания, но цифра та же)

For the Y offset we use the "Math.floor()" javascript function that returns the largest value, smaller than the division result of "curr" to 4. Thus for the months from the same row, the Y offset is the same

http://s4.uploads.ru/t/zbPMQ.png

Гораздо удобнее, если создаваемые текстовые слои, для всех месяцев, будут находится в отдельных одноименных папках. Посему запишем создание таковых папок, имена которые будут получать по очереди из уже готового списка, что мы определили ранее:

http://s4.uploads.ru/t/xuwlo.png

Код:
for(curr=0; curr<12; curr++){

x = curr % 4;
y= Math.floor(curr / 4);

group = doc.layerSets.add();
group.name = months[curr];

}

________________________________

Шаг 8
Далее мы создаем новый текстовый слой внутри группы и определяем его имя соответствующее названию группы текущего месяца:

http://s5.uploads.ru/t/DSqCk.png

Теперь мы должны установить текстовые атрибуты, такие как цвет, размер шрифта и выключку. Мы установим тип ввода нашего текста "PARAGRAPHTEXT" (способ с ограничивающей рамкой) и зададим ему определенные размеры. Атрибут для "contents" в переменной "monthName" обозначает фактический текст, что будет виден в слое - нам же нужно название определенного месяца, поэтому мы поставим и ставим "months[curr]" (напомню любителям, что в программировании часто идут ссылки на определенные, ранее прописанные строки, с их свойствами и значениями… короче атрибутами, чтобы ими воспользоваться. Это как в математике, когда определенный повторяющийся участок формулы мы приравниваем к иксу и далее, для упрощения писанины, используем уже Икс, зная при этом истинное его значение. Так вот, атрибуты для months мы прописали в начале кода, а заданная переменная curr, с каждым витком подставит следующее значение от months):

http://s4.uploads.ru/t/DYMRp.png

Также давайте развернем текст на 90° против часовой стрелки и разместим в определенном месте на слое (здесь мы собираемся использовать наши переменные смещений "x" и "y"):

http://s5.uploads.ru/t/f5FN1.png

Имейте в виду, что расположение сделано относительно верхнего левого угла, но так как мы произвели вращение на 90° против часовой стрелки - это теперь стало нижним левым углом. Если вы задали для своего документа иные размеры, чем в уроке, то вам придется соответственно изменить константы, которые я использовал у себя:
http://s4.uploads.ru/t/Kewf7.png

Код:
for(curr=0; curr<12; curr++){

x = curr % 4;
y= Math.floor(curr / 4);

group = doc.layerSets.add();
group.name = months[curr];

monthName = group.artLayers.add();
monthName.kind = LayerKind.TEXT;
monthName.name = months[curr];
monthName.textItem.color = normalColor;
monthName.textItem.size = 27;
monthName.textItem.kind = TextType.PARAGRAPHTEXT;
monthName.textItem.justification = Justification.RIGHT;
monthName.textItem.height = 35;
monthName.textItem.width = 145;
monthName.textItem.contents = months[curr];
monthName.rotate(-90);
monthName.textItem.position = new Array(55 + 300 * x, (202 + (210 * y)));

}

________________________________

Шаг 9
На этом этапе мы собираемся сделать текстовый слой, который впоследствии будет содержать все даты текущего месяца, кроме воскресений. Сейчас мы сделаем так, чтобы слой был добавлен в ранее созданную группу, а также определим имя слою, выключку, цвет шрифта, размер и позицию расположения. Содержание слоя мы добавим чуть позже и когда мы дойдем до того пункта я объясню почему:

http://s5.uploads.ru/t/kKyul.png

Та же самая процедура и для воскресной колонки, только цвет другой — "highlightColor":

http://s5.uploads.ru/t/ATK8M.png

Код:
days = group.artLayers.add();
days.kind = LayerKind.TEXT;
days.name = "Days";
days.textItem.Justification = Justification.CENTER;
days.textItem.color = normalColor;
days.textItem.size = 19;
days.textItem.position = new Array(85 + (300 * x), 70 + (210 * y));

sundays = group.artLayers.add()
sundays.kind = LayerKind.TEXT;
sundays.name = "Sundays";
sundays.textItem.Justification = Justification.CENTER;
sundays.textItem.color = highlightColor;
sundays.textItem.size = 19;
sundays.textItem.position = new Array(280 + (300 * x), 70 + (210 * y));

}

________________________________

Шаг 10
Теперь мы должны создать две переменные, которые будут вмещать генерируемый текст: переменная "text" будет содержать будние дни, "textSun" — даты воскресенья. Начинаем с добавления заголовков столбиков и отступа первого числа на нужный день недели для первого месяца. Создание новой даты делается через функцию javascript "Date()" — функции года нашего календаря с месяцами и получения их позиции по неделях (я так понял берет данные из "даты и времени" на компьютере). Помните, нумерация всегда начинается от 0, так например, если первым в столбике месяца будет понедельник, то "n" = 0, если это будет вторник, то "n" будет 1 и так далее. Тогда мы должны добавить заявку, которую мы определили вначале к переменной "text", столько раз, сколько необходимо. Например, если первой в месяце будет среда, то мы добавим заявку два раза:

http://s4.uploads.ru/t/TJ5e3.png

Код:
text = monthHeader;
textSun = sundayHeader;

startDate = new Date(year, curr, 1);
n = startDate.getDay();

for(i=0; i<n-1; i++)
text += firstIndent;

}

________________________________

Шаг 11
Настало время для генерации всех чисел месяца. Для этого мы должны знать, сколько дней в каждом конкретном месяце, и нам нужны числа в формате "leading zeros" — таким образом мы должны вернутся и определить две вспомогательных функции: "daysInMonth" и "makeDay". Вернитесь к началу кода и добавьте эти функции.

Функция "daysInMonth" — возвращает нужное количество дней для данного месяца, который мы делаем (определяет их для каждого месяца согласно данным календаря на компьютере).

Функция "makeDay" — возвращает данное ей число в определенном формате (добавляет нули к числам меньшим 10 — в коде это видно из условия, что если d меньше 10, то перед ним нужно добавить цифру 0) и задает расстояние между цифрами чисел дней (в пробелах между кавычками строчки return d + " " — его следуем задавать с учетом пробелов между названиями дней недели).

Так, например, если мы вызовем функцию "daysInMonth" со значением для year = 2010 (год мы указали в параметрах документа) и month = 0 (что будет означать Январь), то мы получим 31 сгенерированный день в заданном месяце. Если мы вызовем функцию "makeDay", например, с d = 3, то это возвратит нам текст "03" (добавит ноль впереди), но если d = 13, то число "13" таким и останется:

http://s4.uploads.ru/t/E60eS.png

Код:
//Определение вспомогательных функций
function daysInMonth(month, year){
return 32 - new Date(month, year, 32).getDate();
}

function makeDay(d){
if(d < 10)
d = "0" + d;
return d + "    ";
}

Спуститесь назад, книзу кода. Мы начнем со значения "d = 1", оно будет увеличиваться, пока не достигнет числа количества дней в месяце. Если "i" равняется шести — это означает, что настало воскресенье, таким образом мы должны добавить дату к воскресному слою. Не забудьте записать "\r", для сноса последующего текста на новую строчку. Иначе в эту же строчку будут добавляться следующие будние числа.

Теперь мы добавляем новую строчку, только если текущий день суббота (i=5). В конце мы должны увеличить обе "i" и "d". При значении "i", когда оно достигает "7", если последним днем было добавлено воскресенье, оно должно вернутся к первому столбику, а значит мы должны указать это, прировняв его к "0" (в этом блоке я мог что-то и неправильно перевести):

http://s4.uploads.ru/t/smGel.png

Теперь у нас есть все даты в наших текстовых переменных и мы можем добавить их к нашим слоям. Причина задержки этого шага заключается в том, что поочередное добавление дат к слою занимает время работы Фотошопа, поэтому лучше добавить все сразу, вместо того, чтобы добавлять каждый день отдельно:

http://s4.uploads.ru/t/kUOyL.png

Код:
d = 1;
while(d <= daysInMonth(curr, year)){
if(i == 6)
textSun += makeDay(d) + "\r";
else{
text += makeDay(d);
if(i == 5)
text += "\r";
}
i++;
d++;
if(i == 7)
i = 0;
}

days.textItem.contents = text;
sundays.textItem.contents = textSun;
}

________________________________

Шаг 12
Итак, код генерации всех чисел месяца готов, а сейчас мы пропишем добавление слоя с годом и создания небольшой линии в основании документа. Для слоя с годом — процедура та же, что мы использовали прежде: создание слоя, переименование, указание размета с цветом и местоположения на холсте:

http://s5.uploads.ru/t/vZJBY.png

Для линии у основания код немного иной. Сначала мы должны определить область с координатами X и Y для всех четырех углов, затем сделать выделение этой области, а потом залить ее и снять выделение:

http://s5.uploads.ru/t/prCUK.png

Код:
// Создание текстового слоя с годом
yearLayer = doc.artLayers.add();
yearLayer.kind = LayerKind.TEXT;
yearLayer.name = year;
yearLayer.textItem.contents = year;
yearLayer.textItem.size = 72;
yearLayer.textItem.color = highlightColor;
yearLayer.textItem.position = new Array(1050, 730);

//Создание нижней линии
line = doc.artLayers.add();
line.name = "Line";
region = Array(Array(80, 729), Array(1000, 729), Array(1000, 730), Array(80, 730));
doc.selection.select(region);
doc.selection.fill(normalColor)
doc.selection.deselect();

________________________________

Шаг 13
Код скрипта готов! Единственная вещь, которую осталось сделать, так это воспользоваться им. Если вы пользовались программой ExtendScript Toolkit, то из выпадающего меню выберите Adobe Photoshop, если при этом фотошоп не запущен, кликните на изображение маленькой цепи, а затем на кнопку запуска. Если Вы использовали любого рода текстовый редактор, вам остается сохранить файл с расширением ".js" или ".jsx", а потом запустить его в фотошопе через меню: File > Scripts > Browse… и выбрать этот сохраненный файл:
http://s4.uploads.ru/t/aygbU.jpg

Здесь торжественно представляю для скачивания получившийся скрипт и сам код, сделанные по уроку. Прочитав урок, вы поймете, как и что можно в нем изменить или какие из его частей использовать в других целях. Проверял на CS3 ex, но не факт что у всех пойдет  script23_calendar.rar ( 2,85 KB )

Код:
//Скрипт для генерации календаря на целый год. 
//Сделан Manoylov-ом AC на основе урока 
//http://psd.tutsplus.com/tutorials/designing-tutorials/create-a-calendar-using-scripting-in-photoshop/

//Определение вспомогательных функций
function daysInMonth(month, year){
return 32 - new Date(month, year, 32).getDate();
}

function makeDay(d){
if(d < 10)
d = "0" + d;
return d + "    ";
}

//параметры документа
width = 1280;
height = 800;
resolution = 72;
docName = "PhotoshopScriptCalendar";
year = 2010;

//Cистема цветов
normalColor = new SolidColor();
normalColor.rgb.hexValue = "FFFFFF";
highlightColor = new SolidColor();
highlightColor.rgb.hexValue = "A4B1D1";
backColor = new SolidColor();
backColor.rgb.hexValue = "1B4392";

//Определение переменных для текстовых слоев
monthHeader = "Пн   Вт    Ср   Чт    Пт    Сб\r\r";  //пробелы между заголовками будут учтены
sundayHeader = "Вс\r\r"; 
//две эрочки - это как два нажатия Enter, а значит числа дней будут начинатся ниже на две строчки.  
firstIndent = "          ";  
// начальный пробел для подгонки первого числа первого месяца на позицию своего дня (узнается опытным путем, так как зависит от размера шрифта и его свойств)
months = new Array("Январь", "Февраль", "Март", "Апрель", "Май", "Июнь", "Июль", "Август", "Сентябрь", "Октябрь", "Ноябрь", "Декабрь");

//Создание нового документа
doc = app.documents.add(width, height, resolution,
docName, NewDocumentMode.RGB);
doc.selection.selectAll();
doc.selection.fill(backColor);
doc.selection.deselect();

//выбор фонового изображения
file = app.openDialog();

if(file[0]){
app.load(file[0]);
backFile = app.activeDocument;
backFile.resizeImage(width, height);
backFile.selection.selectAll();
backFile.selection.copy();
backFile.close(SaveOptions.DONOTSAVECHANGES);
doc.paste();
doc.layers[0].name = "Фоновое изображение";
}

for(curr=0; curr<12; curr++){

x = curr % 4;
y= Math.floor(curr / 4);

group = doc.layerSets.add();
group.name = months[curr];

monthName = group.artLayers.add();
monthName.kind = LayerKind.TEXT;
monthName.name = months[curr];
monthName.textItem.color = normalColor;
monthName.textItem.size = 27;
monthName.textItem.kind = TextType.PARAGRAPHTEXT;
monthName.textItem.justification = Justification.RIGHT;
monthName.textItem.height = 35;
monthName.textItem.width = 145;
monthName.textItem.contents = months[curr];
monthName.rotate(-90);
monthName.textItem.position = new Array(55 + 300 * x, (202 + (210 * y)));

days = group.artLayers.add();
days.kind = LayerKind.TEXT;
days.name = "Days";
days.textItem.Justification = Justification.CENTER;
days.textItem.color = normalColor;
days.textItem.size = 19;
days.textItem.position = new Array(85 + (300 * x), 70 + (210 * y));

sundays = group.artLayers.add()
sundays.kind = LayerKind.TEXT;
sundays.name = "Sundays";
sundays.textItem.Justification = Justification.CENTER;
sundays.textItem.color = highlightColor;
sundays.textItem.size = 19;
sundays.textItem.position = new Array(280 + (300 * x), 70 + (210 * y));

text = monthHeader;
textSun = sundayHeader;

startDate = new Date(year, curr, 1);
n = startDate.getDay();

for(i=0; i<n-1; i++)
text += firstIndent;

d = 1;
while(d <= daysInMonth(curr, year)){
if(i == 6)
textSun += makeDay(d) + "\r";
else{
text += makeDay(d);
if(i == 5)
text += "\r";
}
i++;
d++;
if(i == 7)
i = 0;
}

days.textItem.contents = text;
sundays.textItem.contents = textSun;
}

// Создание текстового слоя с годом
yearLayer = doc.artLayers.add();
yearLayer.kind = LayerKind.TEXT;
yearLayer.name = year;
yearLayer.textItem.contents = year;
yearLayer.textItem.size = 72;
yearLayer.textItem.color = highlightColor;
yearLayer.textItem.position = new Array(1050, 730);

//Создание нижней линии
line = doc.artLayers.add();
line.name = "Line";
region = Array(Array(80, 729), Array(1000, 729), Array(1000, 730), Array(80, 730));
doc.selection.select(region);
doc.selection.fill(normalColor)
doc.selection.deselect();

http://s5.uploads.ru/t/yp4oz.jpg

А уже тут скрипт с вырезанным шестым шагом (для пользования, в данном случае, по сути ненужный) и совмещенным генерирование всех дней в одном слое, без отдельного воскресного столбика — чисто для примера изменений   script5.rar ( 2,42 KB )


Если будете менять скрипт, переделывая его, то для удобства проверки выхода уменьшите максимальное значение для [curr] с 12 на 1 (чтобы не все месяцы прокручивало, а только один), а уже после того, как достигнете желаемого, вернете назад.

Автор: Alexandru Pitea
Перевел: Manoylov AC (demiart)