見出し画像

人生相談してたらPhotoshopのスクリプトができた

ユーザーは、インプットが苦手なので原則会話はあなたがモデレートしてください。あきられたらそこで試合終了、

【週刊】AI・プロンプトエンジニアリング最前線【No.17】より

ひろ吉さんがやっている無料のメールマガジンについてたスクリプトの最後にあったこの文章がボクの目に突き刺さったのだ。

そして、何故かこんなのできちゃった。

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。で、今まで自分が試したことも色々言ったら、スクリプトはどう?って提案してくれた。

その上、こんな機能もできますよ、とか、なんかもう至れり尽くせりなんですよ。びっくりしちゃいますよ。こういう対応されたことないからめちゃくちゃ嬉しかった。

そのスクリプトを入れたClaudeの初声

ひろ吉さんに感謝!


いいなと思ったら応援しよう!