未分類

【Photoshop JavaScript】写真に白枠をつけてアスペクト比を 1 : 1 にするスクリプト

こんにちは!
今回はめずらしく Photoshop の話です。

僕は普段 SNS に写真を投稿するときに白枠を付けるようにしています。
一番大きな理由は Instagram が縦写真のアスペクト比に対応していないから
そこで写真に縦横比1:1の白枠をつけることで、クロップされるのを回避しています。
(ちなみに縦/横位置を英語では portrait / landscape orientation と言うらしいです。これ豆な。)

ほかにも
・撮った機材のキャプションを付ける → 後から何で撮ったかわかる、
・SNS は必要最小限の解像度でいい → 圧縮して容量削減、
・白枠があると写真と周囲に空白ができて、より写真に集中できる、
など利点は多々あります。

…なんですけど、この白枠を付ける作業も数が多くなってくると大変です。
ちなみに僕は白枠を付ける作業は Photoshop でやっていて、もちろんアクション機能も使ってはいますが、それでも写真によってカンバスサイズを調整したりキャプションの位置を調整したりと、どうしても個々に調整が必要な部分が残ってしまいます。

ということで、ようやく重い腰をあげてスクリプトを作りましたよ、というお話です。

さっそく作ってみたので紹介

まず言っておきますが、ズブズブのド素人が作ったコードなのでもう本当に大目に見てやってください > <。
ちょっと調べれば出てくるソースコードをただ繋ぎ合わせただけ、みたいなところあります。
まあ、あくまで個人用ですし。
まともに UI も実装していなければ例外処理もしていないし汎用性も拡張性も考えてません。

さて、言い訳はこれくらいにして紹介していきましょう。
具体的にやっている処理は次の4つです。


① 白枠を作成。写真の長辺に対して、一辺 110% の正方形。
② キャプションを追加。写真右下の白背景に設置。
③ 画像をリサイズ。2000 x 2000 px になるように拡大/縮小。
④ 最後に画像保存。名前に _frame を加えて元写真と同フォルダに出力。


簡単なことしかやっていないのですが、ただこのうち ② のテキストレイヤーのリサイズに関しては Photoshop のクセが相当強く、想定外にかなり苦戦しました。(いまだにここは画像や環境によって正しく動くか不安。)
これくらいなら自分で作った方が速いよ、という方にも多少は参考になる部分はあると思います。

適用例

Before → After の適用例をご覧ください。
リサイズ感については伝わらないと思いますが、白枠とキャプションの付き方については参考になると思います。

まずは横写真への適用例。

Before
After
(白枠の輪郭は出力されません。境界線の見やすさのため表示。)

次に縦写真への適用例です。

Before
After
(白枠の輪郭は出力されません。境界線の見やすさのため表示。)

いかがでしょうか。
ちなみにキャプションのテキストだけは変えられるように UI を作ってあります。
(逆に言うとそれ以外は作ってない)

なかなか悪くないじゃん?って方、ぜひご利用いただけると嬉しいです。
うーんもう少しこうだったらなあ...という方、まあそう焦らずに。
ソースは以下に公開していますので、自分好みにちょっと修正すれば使い物になる可能性はあります。

白枠の倍率・縦横比、キャプションの書体・サイズ、画像のリサイズ倍率、etc... は、ソースコードをいじっていただければいくらでも自分の好みに変えることはできると思います。

ダウンロードはこちらから

[ダウンロードが見つかりません]

※注意:スクリプトのご利用は自己責任でお願いいたします。一切の責任を負いませんのでご了承ください。使い方によっては大切な写真を不可逆に編集してしまう可能性もありますので(どのソフトウェアでも同じですが)、くれぐれもご注意ください。

使い方

① ダウンロードした zip ファイルを展開してください。
② "makeWhiteSquareFrame.jsx" を Photoshop の Scripts フォルダに移動します。
  僕の場合は、"C:\Program Files\Adobe\Adobe Photoshop 2022\Presets\Scripts" でした。
  ※ "makeWhiteSquareFrameAll.jsx" はフォルダ内一括処理の参考スクリプトです。
③ Photoshop を起動します。(既に起動していた場合はプログラムを再起動してください。)
④ 白枠をつけたい写真ファイルを開いてください。
⑤ "ファイル" > "スクリプト" から "makeWhiteSquareFrame" を選択してください。
  処理されたファイルが同じフォルダに保存されます。

ソースコード

中身の説明は面倒なので省略!
ということで、興味のある方はご覧ください。

/*
================================================================================
    makeWhiteSquareFrame.jsx
    2022.05.20
    nuts2u
    Photoshop用スクリプト
      - スクエアの白枠を作成
      - キャプションを挿入
      - リサイズ
      - JPEG保存
================================================================================
*/


//========== 設定と初期化 ==========
var frameRatio = 1.1;                                  //写真長手(px)に対する白枠(px)の比
var redValue = 255, greenValue = 255, blueValue = 255; //写真背景色、すべて255で白
//var captionFontStyle = "MendlSans-DuskThin";           //キャンプションのフォント・フォントスタイル
var captionFontStyle = "YuGothic-Light";               //キャンプションのフォント・フォントスタイル
var captionRatio = 142.7/7772;                         //写真長手(px)に対するフォントサイズ(px)比
var captionTxt = "© 2022 nuts2u. NIKON Z 7, NIKKOR Z 50mm f/1.8 S."; //キャプションテキストの初期値
var spaceRatio = .003;                                 //写真長手(px)に対するキャプション-写真間の隙間(px)の比
var resizeLgth = 2000;                                 //リサイズ後の白枠の大きさ:px
//フラグの初期化
var isPortrait = false; //縦写真の判別フラグ


//========== 単位設定の記憶と変更 ==========
var origRulerUnit = app.preferences.rulerUnits; //初期定規単位
var origTypeUnit  = app.preferences.typeUnits;  //初期テキストサイズ単位
app.preferences.rulerUnits = Units.PIXELS;      //定規単位をピクセルに変更
app.preferences.typeUnits  = TypeUnits.POINTS;  //テキストサイズ単位をポイントに変更


//========== ドキュメントの取得 ==========
try {
    var doc = app.activeDocument;
    //alert(doc.name);
} catch(e) {
    alert(e);
    alert("アクティブドキュメントがありません。");
}


//========== 背景をレイヤに変更 ==========
var lyrs = doc.artLayers;
//一番最後(下)のレイヤが背景
if (lyrs[lyrs.length-1].isBackgroundLayer) {
    //不透明度を設定するとレイヤになる
    lyrs[lyrs.length-1].opacity = 100;
}
lyrs[lyrs.length-1].name = "Picture layer";


//========== スクエアの白枠を作成 ==========
//縦写真は90度左回転(最後に戻す)
if (doc.height > doc.width) {
    isPortrait = true;
    doc.rotateCanvas(-90); //Portrait --> landscape orientation
    //alert("縦写真です") 
}
//回転後の写真の幅と高さ
var picW = doc.width;
//var picH = doc.height;

//カンバスサイズを写真長手×frameRatioの正方形に変更
var frameW = picW*frameRatio; //frameRatioは設定値
var frameH = frameW;
doc.resizeCanvas(frameW, frameH);

var whiteLyr = lyrs.add(); //レイヤを追加
whiteLyr.name = "White layer";

//カラーオブジェクトを作成
var rgbColor = new SolidColor();
rgbColor.rgb.red   = redValue;   //redValueは設定値
rgbColor.rgb.green = greenValue; //greenValueは設定値
rgbColor.rgb.blue  = blueValue;  //blueValueは設定値
//塗りつぶし
doc.selection.selectAll();    //全選択
doc.selection.fill(rgbColor); //塗りつぶし
doc.selection.deselect();     //選択解除
whiteLyr.move(doc.layers[lyrs.length-1], ElementPlacement.PLACEAFTER); //一番下に移動


//========== キャプションの作成 ==========
var txtLyr = lyrs.add(); //レイヤの作成
txtLyr.name = "Caption layer";
txtLyr.kind = LayerKind.TEXT; //テキストレイヤに設定

var txtItm = txtLyr.textItem;
txtItm.antiAliasMethod = AntiAlias.SMOOTH; //エイリアシング: 滑らかに
txtItm.font = captionFontStyle;            //フォント・フォントスタイルの設定、captionFontStyleは設定値
var dpi = doc.resolution;                  //dpiを取得
txtItm.size = (parseFloat(picW))*captionRatio *72/dpi;
                                           //写真に合わせてフォントサイズを変更、captionRatioは設定値
                                           //px-->ptに単位変換のためx72/dpi
txtItm.contents = captionTxt;              //キャプションのテキスト(初期値)を設定、captionTxtは設定値


//========== キャプションのテキスト入力ダイアログ ==========
var activeTxt = txtItm.contents;
var group = new Window("dialog","テキストを入力",{width: 320, height: 150, x: 500, y: 300});
group.okBtn = group.add("button", {width: 80, height: 25, x: 165, y: 105}, "変更", {name:"ok"});
group.cancelBtn = group.add("button", {width: 80, height: 25, x: 75, y: 105}, "キャンセル", {name: "cancel"});
group.captionTxtNew = group.add("edittext", {width: 200, height: 30, x: 60, y: 55}, activeTxt);
group.captionTxtNew.active = true;
// キャンセルボタンクリック時の動作
group.cancelBtn.onClick = function () {
    //alert("キャンセルされました。");
    group.close();
}
//変更ボタンクリック時の動作
group.okBtn.onClick = function () {
    txtItm.contents = group.captionTxtNew.text;
    group.close();
}
group.show();


//========== キャプションを写真右下に移動 ==========
//写真レイヤの右下の座標
//Boundsは境界座標で0~3まである
//0が左端=x1、1が上端=y1、2が右端=x2、3が下端=y2
var picX2 = lyrs.getByName("Picture layer").bounds[2]; 
var picY2 = lyrs.getByName("Picture layer").bounds[3]; 
//テキストレイヤの右上の座標
var txtX2 = txtLyr.bounds[2];
var txtY1 = txtLyr.bounds[1];

//上記座標が合うようにテキストレイヤを移動
txtLyr.translate(picX2-txtX2, picY2-txtY1);
//さらに写真との間に隙間を開けるように下に移動
var space = picW * spaceRatio; //spaceRatioは設定値
txtLyr.translate(0, space); 
//alert("picX2: " + picX2.toString() +"\n"+
//      "picY2: " + picY2.toString() +"\n"+
//      "txtX2: " + txtX2.toString() +"\n"+
//      "txtY1: " + txtY1.toString()       );


//========== 縦写真は90度右回転して戻す ==========
if (isPortrait) {
    doc.rotateCanvas(90); 
}


//========== 画像の保存 ==========
//拡張子を除いた現在のドキュメント名の取得
//var docFileName = doc.name.split(".")[0];
var docFileName = doc.name.substring(0, doc.name.lastIndexOf("."));
//拡張子がなかった場合
if (docFileName.length==0) {
    docFileName = doc.name;
}
//Fileの作成
var docFileNameCopied = docFileName + "_duplicated"; //複製時のファイル名の設定
var saveFilePath = doc.path;                         //同じフォルダ(サブフォルダ等を指定したければここで)
var saveFileName = docFileName + "_frame";           //保存時のファイル名の設定 
var saveFile = new File(saveFilePath + "/" + saveFileName); //Fileをnew

//保存用に画像を複製
doc.duplicate(docFileNameCopied,false); //複製を実行
var docDuplicated =  app.activeDocument;

//画像サイズを変更
docDuplicated.flatten(); //レイヤーを結合
var frameWNew = resizeLgth; //resizeLgth x resizeLgth(px)にリサイズ
var frameHNew = resizeLgth; //resizeLgthは設定値
//var frameHNew = frameJ*(frameWNew/frameW);
docDuplicated.resizeImage(frameWNew, frameHNew, docDuplicated.resolution, ResampleMethod.BICUBIC); //バイキュービック
//出力前の色空間
//var colorProfileBefore = docDuplicated.colorProfileName;

//JPEGSaveOptionsの設定
var jpgSavOpts = new JPEGSaveOptions(); //JPEG保存設定をnew
jpgSavOpts.embedColorProfile = true;    //カラープロファイルの埋め込み
jpgSavOpts.quality = 12;                //画質0~12
jpgSavOpts.formatOptions = FormatOptions.PROGRESSIVE;
                                        //画像が読み込まれる時に上から順に表示されるのがベースライン、
                                        //全体が表示されるけど粗くてだんだん鮮明になっていくのがプログレッシブ
jpgSavOpts.scans = 3;                   //スキャンの段階数 3~5
jpgSavOpts.matte = MatteType.NONE;

//画像保存
activeDocument.saveAs(saveFile, jpgSavOpts, true, Extension.LOWERCASE);
//出力後の色空間
//var colorProfileAfter = docDuplicated.colorProfileName;
//alert("出力前の色空間" + colorProfileBefore +"\n"+
//      "出力後の色空間" + colorProfileBefore       );

//保存用画像を閉じる
docDuplicated.close(SaveOptions.DONOTSAVECHANGES);
docDuplicated = null;
//画像を閉じる
//doc.close(SaveOptions.DONOTSAVECHANGES);
//doc=null;


//========== 元の定規単位設定に復元 ==========
app.preferences.rulerUnits = origRulerUnit;
app.preferences.typeUnits  = origTypeUnit;

あれこれ

僕の場合はキャプションのフォントに "Mendl Sans" という Adobe font を利用しています。
ただ皆さんデフォルトではインストールされていないはずなので、配布ソースコード内では "YuGothic-Light" をとりあえず選択しておきました。(Mac 環境だと何になるかわかりませんが。)
フォントや好みによってキャプションのちょうどいいサイズは変わってくると思いますので、そこのところは適宜修正してください。
ちなみに使いたいフォントの PostScriptName は app.fonts.getByName(layer.textItem.font).postScriptName( layer にはアクティブなテキストレイヤを入れてください)で確認できます。

白枠の比率、リサイズ、出力先についても好みにあわせて変えていただければと。
ただし、ファイル名を変更する時にはくれぐれも上書き保存でデータが消えて困らないようにお気をつけください。

たまに写真と白背景の間に黒縁を入れている方もお見かけしますが(僕はあまり好みではないのでやらない)、もう1つ写真より少しだけ大きいレイヤを作成して、黒で塗り潰したうえで白レイヤと写真レイヤの間に入れてあげればできると思います。

一括で複数枚処理したいなあって方は、たとえば Folder.selectDialog() で選んだフォルダから file オブジェクトのリストを getFiles() して、for ループでひとつずつ app.open(file) してはこの処理を適用、ってすればできるんじゃないかなと思います。
※ 参考までに、フォルダ内の .jpg ファイルを一括で処理する "makeWhiteSquareFrameAll.jsx" も zip ファイルに同梱しています。

参考にさせていただいた主なサイトリンク

最後に参考にしたサイトを列挙しておきます。

  • この記事を書いた人

nuts2u

日々の出来事や撮り溜めた写真を気ままにポストしています。

-未分類