入力した任意の線数でグレースケールをトーン化するPhotoshopJSX

入力した任意の線数でグレースケールをトーン化するPhotoshopJSX

Photoshopで、グレースケールのレイヤー選択→ショートカット起動→線数入力→トーン化という完全俺得スクリプトを作ったのでシェアします。


背景

以前、こういう記事を書きました。

グレースケールのグループを一発でトーン化するアクションという、個人的には画期的な方法だったのですが、使ってるうちに不満も出てくるもので…。

「アクションを線数ごとに用意する必要がある」←実行時に選ぶのがめんどう、「トーン化したいpsdファイルが1つだけ開いている状態で実行しないとダメ」←うっかりやってしまう、などなど微妙な制約が多くて、何よりプログラマ脳な私が「ショートカットでウィンドウ出して線数入力するとか、もっとスマートにならんのかい」とささやき続けていたのです…(;´Д`)

それでPhotoshopのJSXに手を出して、ようやく自分の理想のモノができました!

コード

以下のリンクにあるコードをコピペして任意の名前に「.jsx」という拡張子で保存してください。(「名前を付けてリンク先を保存」でもOKですよ!)

※作成/確認環境: PhotoshopCC2019 Win7/10

作成した.jsxファイルを、以下の記事を参考にスクリプトフォルダに入れて、必要であればショートカットで実行できるようにしてみてください。

私はCtrl+Tでショートカット登録しています。

使い方

グレースケールモードのドキュメントにて、トーン化したいレイヤーを選択します。

スクリプトを実行して線数 (L) を入力、OKを押します(デフォルトは60、10~80の整数のみ対応)。解像度はそのドキュメントの値が使われます(600dpi推奨)。

「○○L」という名前の二階調乗算レイヤーができます。

指定する数値によってトーンの粗さが変わります。

変換元のレイヤーがグループでも動作します。

解説&カスタマイズ支援

長いので全部は載せませんが、カスタマイズしたい場合の参考にどうぞ。

メインコード

//===== メイン =====
try {
	if ( documents.length == 0 ) { //開いているドキュメントがゼロなら
		throw new Error( "ドキュメントが開いていません" );
	}
	var doc = activeDocument; //ドキュメントを取得
	if ( !isSelected( doc ) ) { //選択レイヤーが存在しなければ
		throw new Error( "レイヤーが選択されていません" );
	}
	var ly = doc.activeLayer; //変換元レイヤーを取得
	if ( ly.typename == "LayerSet" && ly.artLayers.length == 0 ){ //変換元がグループかつ中身がなければ
		throw new Error( "グループ内にレイヤーがありません" );
	}
	if( !ly.visible ){ //変換元レイヤーが非表示だったら
		throw new Error( "非表示レイヤーでは実行できません" );
	}
	if ( doc.mode != "DocumentMode.GRAYSCALE" ) { //カラーモードのチェック
		throw new Error( "このスクリプトはグレースケールのみ対応しています" );
	}
	var dpiNum = doc.resolution; //解像度の取得
	var iptL = prompt( "解像度 "+dpiNum+"dpi で処理します。\n線数を指定してください。", "60" ); //入力要求
	if ( !iptL ){ //入力がなければ
		throw new Error( "cancel" );
	}
	var iptL = iptL.replace( /[0-9]/g, function(s) { //全角数字を半角へ変換
		return String.fromCharCode( s.charCodeAt(0) - 65248 );
	});
	if ( !isAcptNum( iptL ) ){ //値チェックNGだったら
		throw new Error( "10~80までの整数で指定してください" );
	}
	var ret = makeTone( iptL, dpiNum, doc ); //トーン化して結果を取得
	if ( !( ret == "success" ) ){ //正常終了していなければ
		throw new Error( ret );
	}
	ly.visible = false; //変換元レイヤーを非表示
} catch ( e ) { //例外処理
	if ( !( e.message == "cancel" ) ){ //「キャンセル」以外の例外なら
		alert( e.message ); //例外メッセージ出力
	}
}

//===== 関数 =====
//↓ここから下は必要なときに呼び出すやつ

上記の部分がメインで動くところ。ハイライトしてあるpromptで入力要求、makeTone関数でトーン化、最後に対象レイヤーを非表示、というのが必要最低限なのですが、ファイルが開いてないとかレイヤー選択されていないとかそういういろんなチェックをクリアしないとそこへたどり着かない作りになっております。

21行目末の「60」が線数にデフォルトで入っている数値なので、いちばんよく使う数値に書き換えてもらえれば良いと思います。

メインコード(別ver)

ちなみに私は、対象を連続して違う線数でトーン化して、それぞれ必要な部分以外をカットするという我流手順があります。そのため、処理後は変換元のレイヤーが表示状態&アクティブになっていてほしいので、以下のようなコードで使っています。

//===== メイン =====
try {
	if ( documents.length == 0 ) { //開いているドキュメントがゼロなら
		throw new Error( "ドキュメントが開いていません" );
	}
	var doc = activeDocument; //ドキュメントを取得
	if ( !isSelected( doc ) ) { //選択レイヤーが存在しなければ
		throw new Error( "レイヤーが選択されていません" );
	}
	var ly = doc.activeLayer; //変換元レイヤーを取得
	if ( ly.typename == "LayerSet" && ly.artLayers.length == 0 ){ //変換元がグループかつ中身がなければ
		throw new Error( "グループ内にレイヤーがありません" );
	}
	if( !ly.visible ){ //変換元レイヤーが非表示だったら
		throw new Error( "非表示レイヤーでは実行できません" );
	}
	if ( doc.mode != "DocumentMode.GRAYSCALE" ) { //カラーモードのチェック
		throw new Error( "このスクリプトはグレースケールのみ対応しています" );
	}
	var dpiNum = doc.resolution; //解像度の取得
	var iptL = prompt( "解像度 "+dpiNum+"dpi で処理します。\n線数を指定してください。", "60" ); //入力要求
	if ( !iptL ){ //入力がなければ
		throw new Error( "cancel" );
	}
	var iptL = iptL.replace( /[0-9]/g, function(s) { //全角数字を半角へ変換
		return String.fromCharCode( s.charCodeAt(0) - 65248 );
	});
	if ( !isAcptNum( iptL ) ){ //値チェックNGだったら
		throw new Error( "10~80までの整数で指定してください" );
	}
	var ret = makeTone( iptL, dpiNum, doc ); //トーン化して結果を取得
	if ( !( ret == "success" ) ){ //正常終了していなければ
		throw new Error( ret );
	}
	doc.activeLayer = ly; //変換元レイヤーをアクティブに
	if ( ly.typename == "LayerSet" ){ //変換元がレイヤーセットだったら
		executeAction( stringIDToTypeID( "collapseAllGroupsEvent" ) ); //すべてのグループを折りたたむ
	}
} catch ( e ) { //例外処理
	if ( !( e.message == "cancel" ) ){ //「キャンセル」以外の例外なら
		alert( e.message ); //例外メッセージ出力
	}
}

//===== 関数 =====
//↓ここから下は必要なときに呼び出すやつ

36~38行目は、変換元レイヤーがグループの場合、アクティブにしたときにグループが開いてしまい、それがわずらわしかったのでグループを折りたたむコードを入れています。ほんとは対象だけたためればよかったんですけど方法が見つからなくて…、仕方なくすべてたたむコードになっております…。

数値チェック関数

メインコードの28行目で呼び出す、入力された値が10~80の整数であるかチェックする部分。

//数値チェック関数
function isAcpt( line ) {
	var regex = new RegExp(/^[0-9]+$/); //正規表現定義
	if ( !regex.test( line ) ){ return false; } //整数チェック
	if ( line < 10 || 80 < line ) { return false; } //範囲チェック
	return true;
}

80Lより大きな数を使いたい場合は5行目の数値を変更すれば指定できるようになります。

トーン化関数

メインコードの31行目で呼び出す、トーン化する部分はこんな感じになってます。

//トーン化関数
function makeTone( line, dpiNum, doc ) {
	try{
		// 1_複製を作成
			var desc1 = new ActionDescriptor();
				var ref1_1 = new ActionReference();
				ref1_1.putClass( charIDToTypeID( "Dcmn" ) );
			desc1.putReference( charIDToTypeID( "null" ), ref1_1 );
			desc1.putString( charIDToTypeID( "Nm  " ), "tmp" );
				var ref1_2 = new ActionReference();
				ref1_2.putEnumerated( charIDToTypeID( "Lyr " ), charIDToTypeID( "Ordn" ), charIDToTypeID( "Trgt" ) );
			desc1.putReference( charIDToTypeID( "Usng" ), ref1_2 );
			desc1.putInteger( charIDToTypeID( "Vrsn" ), 5 );
		executeAction( charIDToTypeID( "Mk  " ), desc1, DialogModes.NO );

		// 2_画像を統合
		var tmpDoc = activeDocument;
		tmpDoc.flatten();

		// 3_モノクロ二階調化
			var desc3 = new ActionDescriptor();
				var desc3_1 = new ActionDescriptor();
				desc3_1.putUnitDouble( charIDToTypeID( "Rslt" ), charIDToTypeID( "#Rsl" ), dpiNum );
				desc3_1.putEnumerated( charIDToTypeID( "Mthd" ), charIDToTypeID( "Mthd" ), charIDToTypeID( "HlfS" ) );
				desc3_1.putUnitDouble( charIDToTypeID( "Frqn" ), charIDToTypeID( "#Rsl" ), line );
				desc3_1.putUnitDouble( charIDToTypeID( "Angl" ), charIDToTypeID( "#Ang" ), 45 );
				desc3_1.putEnumerated( charIDToTypeID( "Shp " ), charIDToTypeID( "Shp " ), charIDToTypeID( "Rnd " ) );
			desc3.putObject( charIDToTypeID( "T   " ), charIDToTypeID( "BtmM" ), desc3_1 );
		executeAction( charIDToTypeID( "CnvM" ), desc3, DialogModes.NO );

		// 4_カンバスを選択
			var desc4 = new ActionDescriptor();
				var ref4_1 = new ActionReference();
				ref4_1.putProperty( charIDToTypeID( "Chnl" ), charIDToTypeID( "fsel" ) );
			desc4.putReference( charIDToTypeID( "null" ), ref4_1 );
			desc4.putEnumerated( charIDToTypeID( "T   " ), charIDToTypeID( "Ordn" ), charIDToTypeID( "Al  " ) );
		executeAction( charIDToTypeID( "setd" ), desc4, DialogModes.NO );

		// 5_コピー
		tmpDoc.activeLayer.copy();

		// 6_元ドキュメントを選択
		activeDocument = doc;

		// 7_ペースト
		doc.paste();

		// 8_複製ドキュメントを閉じる
		tmpDoc.close(SaveOptions.DONOTSAVECHANGES);

		// 9_レイヤー設定変更
		doc.activeLayer.name = line + "L"; //名前
		doc.activeLayer.blendMode= BlendMode.MULTIPLY; //乗算

		return "success";
	} catch ( e ) { //例外処理
		return e.message;
	}
}

対象レイヤー(グループ)を別ドキュメントとして複製、トーン化して元ドキュメントに貼り付けしています。

公開日:2019/06/11

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

コメントは承認制ですので、反映までしばらくお待ち下さい。(稀にスパムの誤判定にて届かないこともあるようですので、必要な際はお問い合わせからお願い致します。)