レイヤー内のピクセル情報から明度を抽出するスクリプト
スクリプト
AnalyzeBrightness.jsx
動作環境:PhotoshopCC2025で動作を確認
選択したレイヤーの明度を解析して、対応したグラデーションマップを制作するスクリプト
グラデーションマップには仮の色が設定されている。
生成されたグラデーションマップのクリッピングマスク設定は手動で対応。
ソースコード
code:jsx
/*
Photoshop Script: 明度解析&グラデーションマップ生成(テキスト生成なし・複数レイヤー対応)
Description: 選択したレイヤーに対し、直上に解析に基づいたグラデーションマップのみを生成します。
Target: Photoshop CC以降 (RGBモード推奨)
*/
#target photoshop
app.bringToFront();
// =======================================================
// 設定エリア
// =======================================================
var CONF = {
posterizeLevels: 10, // 明度をいくつに分類するか
tempSize: 100 // 解析用サイズ(小さいほど高速・大雑把)
};
// =======================================================
// メイン処理
// =======================================================
function main() {
if (app.documents.length === 0) {
alert("ドキュメントが開かれていません。");
return;
}
var doc = app.activeDocument;
if (doc.mode !== DocumentMode.RGB) {
alert("エラー: RGBモードのドキュメントで実行してください。");
return;
}
// 1. 選択されているレイヤーをすべて取得
var selectedLayers = getSelectedLayers(doc);
if (selectedLayers.length === 0) {
selectedLayers = doc.activeLayer;
}
// 2. 一括処理実行
doc.suspendHistory("一括グラデーションマップ作成", "processAllLayers(doc, selectedLayers)");
}
function processAllLayers(doc, layers) {
for (var i = 0; i < layers.length; i++) {
var targetLayer = layersi;
// 対象レイヤーをアクティブにし直す(位置ズレ防止)
doc.activeLayer = targetLayer;
try {
processLayer(doc, targetLayer);
} catch(e) {
// エラー時も続行
}
}
}
function processLayer(doc, layer) {
// 明度解析
var luminanceList = analyzeBrightness(layer);
if (luminanceList.length === 0) return;
luminanceList.sort(function(a, b) { return a - b; });
// グラデーションマップ作成のみ実行
createGradientMap(doc, layer, luminanceList);
}
// -------------------------------------------------------
// 選択レイヤーを取得するヘルパー関数
// -------------------------------------------------------
function getSelectedLayers(doc) {
var selectedLayers = [];
try {
var ref = new ActionReference();
ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));
var desc = executeActionGet(ref);
if (desc.hasKey(stringIDToTypeID("targetLayersIDs"))) {
desc = desc.getList(stringIDToTypeID("targetLayersIDs"));
var count = desc.count;
for (var i = 0; i < count; i++) {
try {
selectedLayers.push(getLayerByID(desc.getReference(i).getIdentifier(charIDToTypeID("LyrId"))));
} catch (e) {}
}
} else {
selectedLayers.push(doc.activeLayer);
}
} catch (e) {
selectedLayers.push(doc.activeLayer);
}
return selectedLayers;
}
function getLayerByID(id) {
var descSelect = new ActionDescriptor();
var refSelect = new ActionReference();
refSelect.putIdentifier(charIDToTypeID("Lyr "), id);
descSelect.putReference(charIDToTypeID("null"), refSelect);
executeAction(charIDToTypeID("slct"), descSelect, DialogModes.NO);
return app.activeDocument.activeLayer;
}
// -------------------------------------------------------
// 明度解析・マップ生成ロジック
// -------------------------------------------------------
function analyzeBrightness(sourceLayer) {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putClass(charIDToTypeID("Dcmn"));
desc.putReference(charIDToTypeID("null"), ref);
desc.putString(charIDToTypeID("Nm "), "TempDoc");
app.activeDocument.activeLayer = sourceLayer;
sourceLayer.copy();
var tempDoc = app.documents.add(app.activeDocument.width, app.activeDocument.height, 72, "TempAnalysis", NewDocumentMode.RGB, DocumentFill.TRANSPARENT);
app.activeDocument = tempDoc;
tempDoc.paste();
var tempLayer = tempDoc.activeLayer;
tempDoc.resizeImage(UnitValue(CONF.tempSize, "px"), undefined, 72, ResampleMethod.BICUBIC);
tempLayer.desaturate();
tempLayer.posterize(CONF.posterizeLevels);
var histo = tempDoc.channels0.histogram;
var foundLevels = [];
for (var i = 0; i < 256; i++) {
if (histoi > 0) foundLevels.push(i);
}
tempDoc.close(SaveOptions.DONOTSAVECHANGES);
app.activeDocument = doc = app.documents0;
app.activeDocument.activeLayer = sourceLayer;
return foundLevels;
}
function createGradientMap(doc, targetLayer, values) {
// 必ず対象レイヤーを選択状態にする(直上に作るため)
doc.activeLayer = targetLayer;
var idMk = charIDToTypeID( "Mk " );
var desc2 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref1 = new ActionReference();
var idAdjL = charIDToTypeID( "AdjL" );
ref1.putClass( idAdjL );
desc2.putReference( idnull, ref1 );
var idUsng = charIDToTypeID( "Usng" );
var desc3 = new ActionDescriptor();
var idType = charIDToTypeID( "Type" );
var desc4 = new ActionDescriptor();
var idGrad = charIDToTypeID( "Grad" );
var desc5 = new ActionDescriptor();
var idNm = charIDToTypeID( "Nm " );
desc5.putString( idNm, "Auto Analyzed Map" );
var idGrdF = charIDToTypeID( "GrdF" );
desc5.putEnumerated( idGrdF, idGrdF, charIDToTypeID( "CstS" ) );
var idIntr = charIDToTypeID( "Intr" );
desc5.putDouble( idIntr, 4096.000000 );
var idClrs = charIDToTypeID( "Clrs" );
var list1 = new ActionList();
for (var i = 0; i < values.length; i++) {
var location = (valuesi / 255) * 4096;
var descColorStop = new ActionDescriptor();
var hue = (360 / values.length) * i;
var autoColor = hsvToRgb(hue, 80, 90);
var idClr = charIDToTypeID( "Clr " );
var descColor = new ActionDescriptor();
var idRd = charIDToTypeID( "Rd " );
descColor.putDouble( idRd, autoColor0 );
var idGrn = charIDToTypeID( "Grn " );
descColor.putDouble( idGrn, autoColor1 );
var idBl = charIDToTypeID( "Bl " );
descColor.putDouble( idBl, autoColor2 );
var idRGBC = charIDToTypeID( "RGBC" );
descColorStop.putObject( idClr, idRGBC, descColor );
var idType = charIDToTypeID( "Type" );
var idClry = charIDToTypeID( "Clry" );
var idUsrS = charIDToTypeID( "UsrS" );
descColorStop.putEnumerated( idType, idClry, idUsrS );
var idLctn = charIDToTypeID( "Lctn" );
descColorStop.putInteger( idLctn, location );
var idMdpn = charIDToTypeID( "Mdpn" );
descColorStop.putInteger( idMdpn, 50 );
var idClrt = charIDToTypeID( "Clrt" );
list1.putObject( idClrt, descColorStop );
}
desc5.putList( idClrs, list1 );
var idTrns = charIDToTypeID( "Trns" );
var list2 = new ActionList();
var descTransStop = new ActionDescriptor();
descTransStop.putInteger( charIDToTypeID( "Opct" ), 100 );
descTransStop.putInteger( charIDToTypeID( "Lctn" ), 0 );
descTransStop.putInteger( charIDToTypeID( "Mdpn" ), 50 );
list2.putObject( charIDToTypeID( "TrnS" ), descTransStop );
var descTransStop2 = new ActionDescriptor();
descTransStop2.putInteger( charIDToTypeID( "Opct" ), 100 );
descTransStop2.putInteger( charIDToTypeID( "Lctn" ), 4096 );
descTransStop2.putInteger( charIDToTypeID( "Mdpn" ), 50 );
list2.putObject( charIDToTypeID( "TrnS" ), descTransStop2 );
desc5.putList( idTrns, list2 );
var idGrdn = charIDToTypeID( "Grdn" );
desc4.putObject( idGrad, idGrdn, desc5 );
var idGdMp = charIDToTypeID( "GdMp" );
desc3.putObject( idType, idGdMp, desc4 );
var idAdjL = charIDToTypeID( "AdjL" );
desc2.putObject( idUsng, idAdjL, desc3 );
executeAction( idMk, desc2, DialogModes.NO );
}
function hsvToRgb(h, s, v) {
var r, g, b;
var i;
var f, p, q, t;
h = Math.max(0, Math.min(360, h));
s = Math.max(0, Math.min(100, s));
v = Math.max(0, Math.min(100, v));
s /= 100;
v /= 100;
if(s == 0) {
r = g = b = v;
return Math.round(r * 255), Math.round(g * 255), Math.round(b * 255);
}
h /= 60;
i = Math.floor(h);
f = h - i;
p = v * (1 - s);
q = v * (1 - s * f);
t = v * (1 - s * (1 - f));
switch(i) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
default: r = v; g = p; b = q;
}
return Math.round(r * 255), Math.round(g * 255), Math.round(b * 255);
}
main();
Geminiに補助してもらって作る
機能要件
レイヤー内のピクセル情報から明度を抽出する機能の定義
1回目の生成物
code:jsx
/*
Photoshop Script: 明度解析&グラデーションマップ生成(パターンB対応版)
Description: 選択レイヤーの明度を解析(ポスタリゼーションで集約)し、数値をテキスト化&マップ生成します。
Target: Photoshop CC以降 (RGBモード推奨)
*/
#target photoshop
app.bringToFront();
// =======================================================
// 設定エリア(好みに合わせて調整可能)
// =======================================================
var CONF = {
posterizeLevels: 6, // 【重要】明度をいくつに分類するか(例: 5〜10くらいが推奨)
tempSize: 100, // 解析用に縮小するサイズ(px)。小さいほど処理が速く、大雑把になる。
TextSize: 12, // 書き出すテキストのフォントサイズ
TextColor: 255, 0, 0 // テキストの色 (R, G, B)
};
// =======================================================
// メイン処理
// =======================================================
function main() {
// 1. エラーチェック
if (app.documents.length === 0) {
alert("ドキュメントが開かれていません。");
return;
}
var doc = app.activeDocument;
if (doc.mode !== DocumentMode.RGB) {
alert("エラー: RGBモードのドキュメントで実行してください。");
return;
}
if (doc.activeLayer.kind === LayerKind.TEXT || doc.activeLayer.isBackgroundLayer) {
// 背景レイヤーやテキストレイヤーでも動くように工夫はできますが、今回は警告のみ
// alert("注意: 背景レイヤーやテキストレイヤーはうまく解析できない場合があります。");
}
// 選択レイヤーを取得(複数選択対応のためのループ準備)
// ※今回はシンプルに「現在アクティブなレイヤー1つ」を対象にします
// ※複数レイヤー対応にする場合はここをループ構造にします
var targetLayer = doc.activeLayer;
// 処理開始
try {
doc.suspendHistory("明度解析とマップ作成", "processLayer(doc, targetLayer)");
} catch (e) {
alert("エラーが発生しました: " + e);
}
}
function processLayer(doc, layer) {
// 2. 解析フェーズ(作業用ドキュメントを作って解析する)
var luminanceList = analyzeBrightness(layer);
if (luminanceList.length === 0) {
alert("明度情報を取得できませんでした。レイヤーが空の可能性があります。");
return;
}
// 数値を昇順にソート
luminanceList.sort(function(a, b) { return a - b; });
// 3. テキストレイヤー作成
createLabelLayer(doc, layer, luminanceList);
// 4. グラデーションマップ作成
createGradientMap(doc, layer, luminanceList);
}
// 明度を解析する関数
function analyzeBrightness(sourceLayer) {
var desc = new ActionDescriptor();
var ref = new ActionReference();
ref.putClass(charIDToTypeID("Dcmn"));
desc.putReference(charIDToTypeID("null"), ref);
desc.putString(charIDToTypeID("Nm "), "TempDoc");
// 現在のレイヤーを複製して新規ドキュメントへ
// (シンプル化のため、選択範囲コピー方式を採用)
app.activeDocument.activeLayer = sourceLayer;
sourceLayer.copy();
var tempDocWidth = new UnitValue(CONF.tempSize, "px");
var tempDocHeight = new UnitValue(CONF.tempSize, "px");
// クリップボードから新規作成だとサイズが不定になるため、
// まず今のドキュメントのピクセル情報を取得する簡易法として
// 「複製」→「統合」→「ポスタリゼーション」→「ヒストグラム取得」を行います。
// レイヤーを新規ドキュメントに複製
var tempDoc = app.documents.add(
app.activeDocument.width,
app.activeDocument.height,
72,
"TempAnalysis",
NewDocumentMode.RGB,
DocumentFill.TRANSPARENT
);
app.activeDocument = tempDoc;
tempDoc.paste();
var tempLayer = tempDoc.activeLayer;
// 画像を縮小(処理高速化 & ノイズ除去)
tempDoc.resizeImage(UnitValue(CONF.tempSize, "px"), undefined, 72, ResampleMethod.BICUBIC);
// グレースケール化(明度のみにする)
tempLayer.desaturate();
// ポスタリゼーション(階調をまとめる!)
// これにより、例えば「120,121,122」が全て「120」等の代表値に変換される
tempLayer.posterize(CONF.posterizeLevels);
// ヒストグラムから使用されている輝度値を取得
var histo = tempDoc.channels0.histogram; // コンポジットまたはRチャンネル
var foundLevels = [];
// 頻度が0より大きい(ピクセルが存在する)輝度値をリストアップ
for (var i = 0; i < 256; i++) {
if (histoi > 0) {
foundLevels.push(i);
}
}
// 解析用ドキュメントを保存せずに閉じる
tempDoc.close(SaveOptions.DONOTSAVECHANGES);
// 元のドキュメントに戻る
app.activeDocument = doc = app.documents0; // 安全策
app.activeDocument.activeLayer = sourceLayer;
return foundLevels;
}
// テキストレイヤーを作成する関数
function createLabelLayer(doc, targetLayer, values) {
var newLayer = doc.artLayers.add();
newLayer.kind = LayerKind.TEXT;
newLayer.name = "明度値: " + targetLayer.name;
var textItem = newLayer.textItem;
textItem.contents = "明度(" + values.length + "階調): " + values.join(", ");
textItem.size = new UnitValue(CONF.TextSize, "pt");
var solidColor = new SolidColor();
solidColor.rgb.red = CONF.TextColor0;
solidColor.rgb.green = CONF.TextColor1;
solidColor.rgb.blue = CONF.TextColor2;
textItem.color = solidColor;
// 位置調整(対象レイヤーの左上付近)
// ※正確なバウンディングボックス取得は複雑なため、キャンバス左上を基準の簡易配置
textItem.position = [targetLayer.bounds0, targetLayer.bounds1];
}
// グラデーションマップを作成する関数
function createGradientMap(doc, targetLayer, values) {
// グラデーションマップはActionManagerでないと細かく制御できないため記述が長くなります
var idMk = charIDToTypeID( "Mk " );
var desc2 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref1 = new ActionReference();
var idAdjL = charIDToTypeID( "AdjL" );
ref1.putClass( idAdjL );
desc2.putReference( idnull, ref1 );
var idUsng = charIDToTypeID( "Usng" );
var desc3 = new ActionDescriptor();
var idType = charIDToTypeID( "Type" );
var desc4 = new ActionDescriptor();
var idGrad = charIDToTypeID( "Grad" );
var desc5 = new ActionDescriptor();
var idNm = charIDToTypeID( "Nm " );
desc5.putString( idNm, "Auto Analyzed Map" );
var idGrdF = charIDToTypeID( "GrdF" );
var idGrdF = charIDToTypeID( "GrdF" );
desc5.putEnumerated( idGrdF, idGrdF, charIDToTypeID( "CstS" ) );
var idIntr = charIDToTypeID( "Intr" );
desc5.putDouble( idIntr, 4096.000000 );
// --- カラー分岐点(Clrs)の設定 ---
var idClrs = charIDToTypeID( "Clrs" );
var list1 = new ActionList();
// 解析した明度値ごとにポイントを作成
for (var i = 0; i < values.length; i++) {
var location = (valuesi / 255) * 4096; // 0-4096スケールに変換
var descColorStop = new ActionDescriptor();
// 色を自動生成(仮の色:HSLを回して虹色のように変化させる)
var hue = (360 / values.length) * i;
var autoColor = hsvToRgb(hue, 80, 90); // 彩度80, 明度90で固定
var idClr = charIDToTypeID( "Clr " );
var descColor = new ActionDescriptor();
var idRd = charIDToTypeID( "Rd " );
descColor.putDouble( idRd, autoColor0 );
var idGrn = charIDToTypeID( "Grn " );
descColor.putDouble( idGrn, autoColor1 );
var idBl = charIDToTypeID( "Bl " );
descColor.putDouble( idBl, autoColor2 );
var idRGBC = charIDToTypeID( "RGBC" );
descColorStop.putObject( idClr, idRGBC, descColor );
var idType = charIDToTypeID( "Type" );
var idClry = charIDToTypeID( "Clry" );
var idUsrS = charIDToTypeID( "UsrS" );
descColorStop.putEnumerated( idType, idClry, idUsrS );
var idLctn = charIDToTypeID( "Lctn" );
descColorStop.putInteger( idLctn, location );
var idMdpn = charIDToTypeID( "Mdpn" );
descColorStop.putInteger( idMdpn, 50 );
var idClrt = charIDToTypeID( "Clrt" );
list1.putObject( idClrt, descColorStop );
}
desc5.putList( idClrs, list1 );
// --- 透明度(Trns)の設定(今回は不透明度100%固定) ---
var idTrns = charIDToTypeID( "Trns" );
var list2 = new ActionList();
var descTransStop = new ActionDescriptor();
descTransStop.putInteger( charIDToTypeID( "Opct" ), 100 );
descTransStop.putInteger( charIDToTypeID( "Lctn" ), 0 );
descTransStop.putInteger( charIDToTypeID( "Mdpn" ), 50 );
list2.putObject( charIDToTypeID( "TrnS" ), descTransStop );
var descTransStop2 = new ActionDescriptor();
descTransStop2.putInteger( charIDToTypeID( "Opct" ), 100 );
descTransStop2.putInteger( charIDToTypeID( "Lctn" ), 4096 );
descTransStop2.putInteger( charIDToTypeID( "Mdpn" ), 50 );
list2.putObject( charIDToTypeID( "TrnS" ), descTransStop2 );
desc5.putList( idTrns, list2 );
var idGrdn = charIDToTypeID( "Grdn" );
desc4.putObject( idGrad, idGrdn, desc5 );
var idGdMp = charIDToTypeID( "GdMp" );
desc3.putObject( idType, idGdMp, desc4 );
var idAdjL = charIDToTypeID( "AdjL" );
desc2.putObject( idUsng, idAdjL, desc3 );
executeAction( idMk, desc2, DialogModes.NO );
}
// 簡易ヘルパー:HSVをRGBに変換(仮の色を作る用)
function hsvToRgb(h, s, v) {
var r, g, b;
var i;
var f, p, q, t;
h = Math.max(0, Math.min(360, h));
s = Math.max(0, Math.min(100, s));
v = Math.max(0, Math.min(100, v));
s /= 100;
v /= 100;
if(s == 0) {
r = g = b = v;
return Math.round(r * 255), Math.round(g * 255), Math.round(b * 255);
}
h /= 60;
i = Math.floor(h);
f = h - i;
p = v * (1 - s);
q = v * (1 - s * f);
t = v * (1 - s * (1 - f));
switch(i) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
default: r = v; g = p; b = q;
}
return Math.round(r * 255), Math.round(g * 255), Math.round(b * 255);
}
// 実行
main();
public.icon