# Обработка сканов фотографии в Photoshop

> Заставляем Photoshop работать за вас: как написать скрипт на JavaScript, который сам собирает пачку сканов в одно идеальное фото, обрезает края и чистит мусор. Код прилагается.

**Published:** 2022-02-05

**Canonical:** <https://grishy.dev/ru/posts/photoshop-scan/>

**Translations:** [Русский](https://grishy.dev/ru/posts/photoshop-scan.md)

![Обработка сканов фотографии в Photoshop](https://grishy.dev/_astro/preview.6Liq_AxT.avif)

---

Недавно у меня возникла необходимость отсканировать большое количество фотографий в хорошем качестве.
Это фото с семейного альбома. Я приобрел принтер Epson Perfection V600 Photo. Он отлично сканирует, но иногда остается большое количество мелких соринок и прочих проблем сканирования. Где то цвета не те, где то фото обрезалось при сканировании. Для этого я решил делать по несколько сканов одной фотографии в разных положениях и потом соединять их в одну.

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

---

![Сканер Epson Perfection V600 Photo](https://grishy.dev/_astro/scaner.D3iRD5r0.avif)
Мой сканер.

---

## Общая схема работы

Общая идея работы была такая, сделать 4 фото, объединить их в одну. Для этого я буду делать сразу сканы 4 фото параллельно. Это позволяет сделать сканер. Они автоматически будут разделены.

На схеме синим подписаны фотографии.

![Схема расположения фото при сканировании](https://grishy.dev/_astro/scheme.Bq0eJKue.avif)

Я делаю сканы без сжатия, в `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.
```

![Меню File → Scripts → Load Files into Stack](https://grishy.dev/_astro/load-files-into-stack.BI5os4Cr.avif)

При добавлении фото можно, можно отметить опции по выравниванию фото и сразу объединения их в один объект.

![Опции загрузки фото в Photoshop](https://grishy.dev/_astro/load-images-into-photoshop.DG0Xxnt-.avif)

### Мой скрипт

На основе этого был сделан скрипт, в котором нужно указать папку `scan`. После этого он будет автоматически объединять все фото в папке, применять параметры объединения и сохранять результат в папку `output`. Кстати, для создания скрипта использовались функции Photoshop по логированию всех действий в файл. Поэтому такие странные имена в функции `parse`. Вывод логов идёт в файл `ScriptingListenerJS.log` на рабочем столе.

Я оставлю комментарии прямо в коде скрипта. Для запуска нужно сохранить этот код с расширением `.jsx`

```js
// Не отображать окна для подтверждения
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 = "~/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);
}
```

### Запуск

После первого запуска я получил проблемы нехватки места...
![Нехватка дискового пространства](https://grishy.dev/_astro/storage.do-Vjh0C.avif)

Во втором месте, я решил замерить, как оно по потреблению памяти. Я решил не закрывать фото, стало интересно, как оно будет по потреблению памяти.
![Потребление памяти в Activity Monitor](https://grishy.dev/_astro/activity.C5QrBflw.avif)

Кстати, оказалось, что есть плагин для VSCode для отладки таких скриптов. Они могут запускать их в Debug-режиме. Мне это очень помогло в разработке скрипта.
![Отладка скриптов Photoshop в VSCode](https://grishy.dev/_astro/vscode.C4YXuHfv.avif)

## Результат

![Результат объединения сканов](https://grishy.dev/_astro/result.B5PEadsP.avif)
