人生相談してたらPhotoshopのスクリプトができた
ユーザーは、インプットが苦手なので原則会話はあなたがモデレートしてください。あきられたらそこで試合終了、
ひろ吉さんがやっている無料のメールマガジンについてたスクリプトの最後にあったこの文章がボクの目に突き刺さったのだ。
そして、何故かこんなのできちゃった。

下をコピペしてエディタに貼り付け、design_analyzer.jsx とか名前を付けて保存。
Photoshopの「ファイル」→「スクリプト」→「参照」からこのファイルを指定すると動きます。
※ PC版PSDとSP版PSDの2ファイル開いておく必要があります。
// エラーログ配列の初期化
var errorLog = [];
// デザイン情報の構造を初期化
var designInfo = {
pc: {
fontStyles: [], // {font, size, color}の形式で保存
colors: [],
fontSizes: []
},
sp: {
fontStyles: [],
colors: [],
fontSizes: []
}
};
// フォントサイズを整形する関数
function formatFontSize(size) {
return Number(size).toFixed(2);
}
// カラーを16進数に変換
function rgbToHex(r, g, b) {
function toHex(n) {
var hex = Math.round(n).toString(16);
return hex.length === 1 ? "0" + hex : hex;
}
return "#" + toHex(r) + toHex(g) + toHex(b);
}
// フォント情報を追加する関数
function addFontInfo(deviceType, font, size, color) {
try {
var sizeNum = parseFloat(size);
var formattedSize = formatFontSize(sizeNum);
// フォントスタイルの追加
designInfo[deviceType].fontStyles.push({
font: font,
size: formattedSize,
color: color
});
// フォントサイズの追加(重複チェック)
var sizeExists = false;
for (var i = 0; i < designInfo[deviceType].fontSizes.length; i++) {
if (designInfo[deviceType].fontSizes[i] === formattedSize) {
sizeExists = true;
break;
}
}
if (!sizeExists) {
designInfo[deviceType].fontSizes.push(formattedSize);
}
// カラーの追加(重複チェック)
var colorExists = false;
for (var i = 0; i < designInfo[deviceType].colors.length; i++) {
if (designInfo[deviceType].colors[i] === color) {
colorExists = true;
break;
}
}
if (!colorExists) {
designInfo[deviceType].colors.push(color);
}
} catch(e) {
errorLog.push("フォント追加エラー: " + font + " (" + e + ")");
}
}
// レイヤー処理関数
function processLayer(layer, deviceType) {
try {
if (layer.typename === "LayerSet") {
for (var i = 0; i < layer.layers.length; i++) {
processLayer(layer.layers[i], deviceType);
}
} else if (layer.kind === LayerKind.TEXT) {
var textItem = layer.textItem;
var textColor = rgbToHex(
textItem.color.rgb.red,
textItem.color.rgb.green,
textItem.color.rgb.blue
);
addFontInfo(deviceType, textItem.font, textItem.size.value, textColor);
}
} catch(e) {
errorLog.push("レイヤー処理エラー: " + layer.name + " (" + e + ")");
}
}
// ドキュメント解析関数
// ドキュメント解析関数を修正
function analyzeDocument(doc) {
// ドキュメントの種類を判定(860px基準)
var deviceType = (doc.width.value <= 860) ? "sp" : "pc";
// デバッグ用に情報を記録
errorLog.push("情報: " + doc.name + " - 幅: " + doc.width.value + "px → " + deviceType + "として処理");
function processLayer(layer, deviceType) { // deviceTypeを引数として追加
try {
if (layer.typename === "LayerSet") {
for (var i = 0; i < layer.layers.length; i++) {
processLayer(layer.layers[i], deviceType); // deviceTypeを渡す
}
} else if (layer.kind === LayerKind.TEXT) {
var textItem = layer.textItem;
var textColor = rgbToHex(
textItem.color.rgb.red,
textItem.color.rgb.green,
textItem.color.rgb.blue
);
// デバイスタイプを明示的に指定
addFontInfo(deviceType, textItem.font, textItem.size.value, textColor);
// デバッグ情報を追加
errorLog.push("追加: " + deviceType + " - " + textItem.font + " " +
textItem.size.value + "px " + textColor);
}
} catch(e) {
errorLog.push("レイヤー処理エラー: " + layer.name + " (" + e + ")");
}
}
// deviceTypeを明示的に渡す
for (var i = 0; i < doc.layers.length; i++) {
processLayer(doc.layers[i], deviceType);
}
}
// 保存処理関数を修正
function saveFile(content, defaultName, extension) {
try {
var file = File.saveDialog("ファイルを保存", "デザイン情報." + extension);
if (file) {
// 新しいFileオブジェクトを作成
var saveFile = new File(file.absoluteURI);
saveFile.encoding = "UTF8";
saveFile.open("w");
saveFile.write(content);
saveFile.close();
alert("保存が完了しました!");
}
} catch(e) {
alert("ファイルの保存中にエラーが発生しました: " + e);
}
}
// 各フォーマットの出力関数
function formatAsJSON() {
try {
var jsonStr = "{\n";
// PC版情報
jsonStr += ' "pc": {\n';
// PC フォントスタイル
jsonStr += ' "fontStyles": [\n';
for (var i = 0; i < designInfo.pc.fontStyles.length; i++) {
var style = designInfo.pc.fontStyles[i];
jsonStr += ' {\n';
jsonStr += ' "font": "' + style.font + '",\n';
jsonStr += ' "size": "' + style.size + '",\n';
jsonStr += ' "color": "' + style.color + '"\n';
jsonStr += ' }' + (i < designInfo.pc.fontStyles.length - 1 ? ',' : '') + '\n';
}
jsonStr += ' ],\n';
// PC カラー
jsonStr += ' "colors": [\n';
for (var i = 0; i < designInfo.pc.colors.length; i++) {
jsonStr += ' "' + designInfo.pc.colors[i] + '"' +
(i < designInfo.pc.colors.length - 1 ? ',' : '') + '\n';
}
jsonStr += ' ],\n';
// PC フォントサイズ
jsonStr += ' "fontSizes": [\n';
for (var i = 0; i < designInfo.pc.fontSizes.length; i++) {
jsonStr += ' "' + designInfo.pc.fontSizes[i] + '"' +
(i < designInfo.pc.fontSizes.length - 1 ? ',' : '') + '\n';
}
jsonStr += ' ]\n';
jsonStr += ' },\n';
// SP版情報
jsonStr += ' "sp": {\n';
// SP フォントスタイル
jsonStr += ' "fontStyles": [\n';
for (var i = 0; i < designInfo.sp.fontStyles.length; i++) {
var style = designInfo.sp.fontStyles[i];
jsonStr += ' {\n';
jsonStr += ' "font": "' + style.font + '",\n';
jsonStr += ' "size": "' + style.size + '",\n';
jsonStr += ' "color": "' + style.color + '"\n';
jsonStr += ' }' + (i < designInfo.sp.fontStyles.length - 1 ? ',' : '') + '\n';
}
jsonStr += ' ],\n';
// SP カラー
jsonStr += ' "colors": [\n';
for (var i = 0; i < designInfo.sp.colors.length; i++) {
jsonStr += ' "' + designInfo.sp.colors[i] + '"' +
(i < designInfo.sp.colors.length - 1 ? ',' : '') + '\n';
}
jsonStr += ' ],\n';
// SP フォントサイズ
jsonStr += ' "fontSizes": [\n';
for (var i = 0; i < designInfo.sp.fontSizes.length; i++) {
jsonStr += ' "' + designInfo.sp.fontSizes[i] + '"' +
(i < designInfo.sp.fontSizes.length - 1 ? ',' : '') + '\n';
}
jsonStr += ' ]\n';
jsonStr += ' }\n';
jsonStr += '}';
return jsonStr;
} catch(e) {
alert("JSON形式への変換中にエラーが発生しました: " + e);
return "{}";
}
}
function formatAsYAML() {
var yaml = "document_info:\n";
// PC版情報
yaml += " pc:\n";
yaml += " fonts:\n";
for (var i = 0; i < designInfo.pc.fontStyles.length; i++) {
var style = designInfo.pc.fontStyles[i];
yaml += " - font: " + style.font + "\n";
yaml += " size: " + style.size + "\n";
yaml += " color: " + style.color + "\n";
}
// SP版情報を追加
yaml += " sp:\n";
yaml += " fonts:\n";
for (var i = 0; i < designInfo.sp.fontStyles.length; i++) {
var style = designInfo.sp.fontStyles[i];
yaml += " - font: " + style.font + "\n";
yaml += " size: " + style.size + "\n";
yaml += " color: " + style.color + "\n";
}
return yaml;
}
function formatAsXML() {
var xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
xml += '<designSystem>\n';
// PC版情報
xml += ' <pc>\n';
xml += ' <fonts>\n';
for (var i = 0; i < designInfo.pc.fontStyles.length; i++) {
var style = designInfo.pc.fontStyles[i];
xml += ' <font name="' + style.font + '" size="' + style.size + '" color="' + style.color + '" />\n';
}
xml += ' </fonts>\n';
xml += ' </pc>\n';
// SP版情報を追加
xml += ' <sp>\n';
xml += ' <fonts>\n';
for (var i = 0; i < designInfo.sp.fontStyles.length; i++) {
var style = designInfo.sp.fontStyles[i];
xml += ' <font name="' + style.font + '" size="' + style.size + '" color="' + style.color + '" />\n';
}
xml += ' </fonts>\n';
xml += ' </sp>\n';
xml += '</designSystem>';
return xml;
}
function formatAsCSS() {
var css = ":root {\n";
// カラー変数
css += " /* Colors */\n";
for (var i = 0; i < designInfo.pc.colors.length; i++) {
var color = designInfo.pc.colors[i];
css += " --color-" + (i + 1) + ": " + color + ";\n";
}
css += "}\n";
return css;
}
// メイン処理
try {
if (app.documents.length === 0) {
alert("開いているドキュメントがありません。");
} else {
// 処理中表示
alert("デザイン情報を解析しています...");
// 開いているドキュメントを処理
for (var i = 0; i < app.documents.length; i++) {
var doc = app.documents[i];
analyzeDocument(doc);
}
// 結果を表示するダイアログを作成
var resultDialog = new Window("dialog", "解析結果");
// メインのテキストエリア
var mainGroup = resultDialog.add("group");
mainGroup.orientation = "column";
mainGroup.alignChildren = "fill";
var resultText = "=== デザイン解析結果 ===\n\n";
// フォントスタイル情報
resultText += "【スタイル一覧】\n";
resultText += "PC版:\n";
for (var i = 0; i < designInfo.pc.fontStyles.length; i++) {
var style = designInfo.pc.fontStyles[i];
resultText += style.font + " " + style.size + "px " + style.color + "\n";
}
resultText += "\nSP版:\n";
for (var i = 0; i < designInfo.sp.fontStyles.length; i++) {
var style = designInfo.sp.fontStyles[i];
resultText += style.font + " " + style.size + "px " + style.color + "\n";
}
// カラー一覧
resultText += "\n【カラー一覧】\n";
resultText += "PC版: " + designInfo.pc.colors.join(", ") + "\n";
resultText += "SP版: " + designInfo.sp.colors.join(", ") + "\n";
var resultTextArea = mainGroup.add("edittext", undefined, resultText, {multiline: true, scrolling: true});
resultTextArea.preferredSize = [500, 400];
// エラーログの表示(エラーがある場合のみ)
if (errorLog.length > 0) {
resultText += "\n【警告】\n" + errorLog.join("\n");
resultTextArea.text = resultText;
}
// エクスポートボタングループ1
var exportGroup1 = resultDialog.add("group");
exportGroup1.orientation = "row";
exportGroup1.alignChildren = "center";
var saveJSONButton = exportGroup1.add("button", undefined, "JSONで保存");
var saveYAMLButton = exportGroup1.add("button", undefined, "YAMLで保存");
var saveXMLButton = exportGroup1.add("button", undefined, "XMLで保存");
// ボタンのイベントハンドラを設定
saveJSONButton.onClick = function() { saveFile(formatAsJSON(), "design_system", "json"); }
saveYAMLButton.onClick = function() { saveFile(formatAsYAML(), "design_system", "yaml"); }
saveXMLButton.onClick = function() { saveFile(formatAsXML(), "design_system", "xml"); }
// 閉じるボタン
var closeButton = resultDialog.add("button", undefined, "閉じる");
closeButton.onClick = function() { resultDialog.close(); }
resultDialog.show();
}
} catch(e) {
alert("重大なエラーが発生しました: " + e);
}補足
XDもFigmaもZeplinも、いろんなもの試して読み込めない複雑なレイヤー構造のPSD。で、今まで自分が試したことも色々言ったら、スクリプトはどう?って提案してくれた。
その上、こんな機能もできますよ、とか、なんかもう至れり尽くせりなんですよ。びっくりしちゃいますよ。こういう対応されたことないからめちゃくちゃ嬉しかった。

【AIに全部書かせるニュースレター】@HIROKICHI_PD #みんなのニュースレターhttps://t.co/UxNMLKL9yr
— ひろ吉🎨 (@HIROKICHI_PD) November 16, 2024
ひろ吉さんに感謝!
