Недавно у меня возникла необходимость отсканировать большое количество фотографий в хорошем качестве. Это фото с семейного альбома. Я приобрел принтер Epson Perfection V600 Photo. Он отлично сканирует, но иногда остается большое количетсво мелких соринок и прочих проблем сканирования. Где то цвета не те, где то фото обрезалось при сканировании. Для этого я решил делать по несколько сканов одной фотографии в разных положениях и потом соединять их в одну.
В принципе, все логично. Так как информации у нас больше, то можно получить намного лучше качество изображения. Но как сделать это, оставалось непонятно. В итоге я пришел к решению, которое автоматическо соединяло фото из папки и сохраняло в другую. Фотограций было, да и сейчас есть, много.
Мой сканер.
Общая схема работы
Общая идея работы была такая, сделать 4 фото, объединить их в одну. Для этого я буду делать сразу сканы 4 фото параллельно. Это позволяет сделать сканер. Они автоматически будут разделены.
На схеме синим подписаны фотографии.
Я делаю сканы без сжатия, в tif
формате. Получается нам нужно сделать 4 перехода, как показано выше. У нас будет 4 фото на 4 сканирования => 16 фото.
После сканирования у меня получается большое кол-во файлов в папке сканирования. Я разделяю их по папкам для каждой фото.
> tree scan
scan
├── 1
│ ├── 1.tif
│ ├── 5.tif
│ ├── 7.tif
│ └── 9.tif
└── 2
├── 2.tif
├── 4.tif
├── 6.tif
└── 8.tif
Дальше нужно соединить фото в папке и сохранить ее в другую папку.
Photoshop автоматизация
Я помнил, что в Photoshop можно сделать автоматизацию. Но перед этим нужно было как их объединить. Оказалось, что уже есть возможность загрузить и объединить несколько фото. Это Load Files into Stack
.
Going to File > Scripts > Load Files into Stack.
При добавлении фото можно, можно отметить опции по выравниванию фото и сразу объединения их в один объект.
Мой скрипт
На основе этого был сделан скрип, в котором нужно указать папку scan
. После этого он будет автоматически объединять все фото в папке и применять параметры объединения. И сохраняет в папку output
. Кстати, для создания скрипта использовались функции Photoshop по логированию всех действий в файл. По этому такие странные имена в функции parse
. Вывод идет логов в файл ScriptingListenerJS.log
на рабочем столе.
Я оставлю комментарии прямо в коде скрипта. Для запуска нужно сохранить это код с расширением .jsx
// Не отображать окна для подтверждения
app.displayDialogs = DialogModes.NO;
// Загрузить уже существующий скрипт
loadLayersFromScript = true;
var SCRIPTS_FOLDER = decodeURI(
app.path + "/" + localize("$$$/ScriptingSupport/InstalledScripts=Presets/Scripts"),
);
$.evalFile(new File(SCRIPTS_FOLDER + "/Load Files into Stack.jsx"));
// Куда нужно сохранять изображения
var output = "/Users/grishy/Desktop/result/Untitled-";
// Начальный индекс для фото
// Получается Untitled-1 Untitled-2 Untitled-3
var startInx = 1;
// Выбираем папку с папками (scan)
var dirFolder = Folder.selectDialog("Select a folder to process");
// Фильтр, чтобы выбрать только папки
var dirList = dirFolder.getFiles(function (f) {
return f instanceof Folder;
});
// Начать объединения всех фото
var len = dirList.length;
for (var i = 0; i < len; i++) {
var el = dirList[i];
// Вывести сообщение в консоль
$.writeln("> Selected photos (" + i + 1 + "/" + len + "): " + el);
parse(Folder(el));
startInx++;
}
function parse(inputFolder) {
// Берем все фото
var filesList = inputFolder.getFiles("*.tif");
$.writeln("> Selected photos:");
$.writeln(filesList);
if (filesList == null) return;
// Создаем объединение
loadLayers.createSmartObject = true;
loadLayers.intoStack(filesList, true);
// =======================================================
// Обрезаем пустые места с краев, crop
var idtrim = stringIDToTypeID("trim");
var desc4 = new ActionDescriptor();
var idtrimBasedOn = stringIDToTypeID("trimBasedOn");
var idtrimBasedOn = stringIDToTypeID("trimBasedOn");
var idtransparency = stringIDToTypeID("transparency");
desc4.putEnumerated(idtrimBasedOn, idtrimBasedOn, idtransparency);
var idtop = stringIDToTypeID("top");
desc4.putBoolean(idtop, true);
var idbottom = stringIDToTypeID("bottom");
desc4.putBoolean(idbottom, true);
var idleft = stringIDToTypeID("left");
desc4.putBoolean(idleft, true);
var idright = stringIDToTypeID("right");
desc4.putBoolean(idright, true);
executeAction(idtrim, desc4, DialogModes.NO);
// =======================================================
// Объединяем их по среднему значению в каждом пикселе
var idapplyImageStackPluginRenderer = stringIDToTypeID("applyImageStackPluginRenderer");
var desc2 = new ActionDescriptor();
var idimageStackPlugin = stringIDToTypeID("imageStackPlugin");
var idavrg = charIDToTypeID("avrg");
desc2.putClass(idimageStackPlugin, idavrg);
var idname = stringIDToTypeID("name");
desc2.putString(idname, "Mean");
executeAction(idapplyImageStackPluginRenderer, desc2, DialogModes.NO);
// =======================================================
// Сохраняем фото в папку, как png
var idsave = stringIDToTypeID("save");
var desc4 = new ActionDescriptor();
var idas = stringIDToTypeID("as");
var desc5 = new ActionDescriptor();
var idmethod = stringIDToTypeID("method");
var idPNGMethod = stringIDToTypeID("PNGMethod");
var idquick = stringIDToTypeID("quick");
desc5.putEnumerated(idmethod, idPNGMethod, idquick);
var idPNGInterlaceType = stringIDToTypeID("PNGInterlaceType");
var idPNGInterlaceType = stringIDToTypeID("PNGInterlaceType");
var idPNGInterlaceNone = stringIDToTypeID("PNGInterlaceNone");
desc5.putEnumerated(idPNGInterlaceType, idPNGInterlaceType, idPNGInterlaceNone);
var idPNGFilter = stringIDToTypeID("PNGFilter");
var idPNGFilter = stringIDToTypeID("PNGFilter");
var idPNGFilterAdaptive = stringIDToTypeID("PNGFilterAdaptive");
desc5.putEnumerated(idPNGFilter, idPNGFilter, idPNGFilterAdaptive);
var idcompression = stringIDToTypeID("compression");
desc5.putInteger(idcompression, 6);
var idPNGFormat = stringIDToTypeID("PNGFormat");
desc4.putObject(idas, idPNGFormat, desc5);
var idin = stringIDToTypeID("in");
desc4.putPath(idin, new File(output + startInx + ".png"));
var iddocumentID = stringIDToTypeID("documentID");
desc4.putInteger(iddocumentID, 223);
var idlowerCase = stringIDToTypeID("lowerCase");
desc4.putBoolean(idlowerCase, true);
var idsaveStage = stringIDToTypeID("saveStage");
var idsaveStageType = stringIDToTypeID("saveStageType");
var idsaveSucceeded = stringIDToTypeID("saveSucceeded");
desc4.putEnumerated(idsaveStage, idsaveStageType, idsaveSucceeded);
executeAction(idsave, desc4, DialogModes.NO);
return;
// =======================================================
// Закрываем фото, для экономии памяти
var idclose = stringIDToTypeID("close");
var desc3 = new ActionDescriptor();
var idsaving = stringIDToTypeID("saving");
var idyesNo = stringIDToTypeID("yesNo");
var idno = stringIDToTypeID("no");
desc3.putEnumerated(idsaving, idyesNo, idno);
var iddocumentID = stringIDToTypeID("documentID");
desc3.putInteger(iddocumentID, 258);
var idforceNotify = stringIDToTypeID("forceNotify");
desc3.putBoolean(idforceNotify, true);
executeAction(idclose, desc3, DialogModes.NO);
}
Запуск
После первого запуска я получил проблемы нехватки места...
Во втором месте, я решил замерить, как оно по потреблению памяти. Я решил не закрывать фото, стало интересно, как оно будет по потреблению памяти.
Кстати, оказалось что есть плагин для VSCode для отладки таких скриптов. Они могу запускать их в Debug режиме. Мне очень помогло в разработки скрипта.