1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Photoshopでリアルタイムに総インキ量チェック/白飛びチェックするスクリプト

Last updated at Posted at 2025-04-27

総インキ量チェックをリアルタイムに!

CMYK画像に、リアルタイムで総インキ量(TAC)を可視化するグループを追加します。
白飛びも分るようになります。
RGB画像では黒つぶれ・白飛びを可視化します。

動作・RGB

実行すると、RGB値が全て0になる(潰れる)ところは青、RGB値が全て最大になるところは赤で表示します(Lightroom等と同じ)

SS_ 2025-04-27 13.19.29.png

SS_ 2025-04-27 13.19.12.png

動作・CMYK

実行時に総インキ量設定値を聞いてきます。
SS_Safari_20250427-141942.png

TAC値オーバーするところを青で、白飛びするところを赤で表示します。
SS_ 2025-04-27 14.20.29.png

SS_ 2025-04-27 14.20.19.png

動作②

実行したときに、既にClipping_Viewフォルダがある場合は、
・最上部にない場合は一番上に移動
・表示・非表示を切り替え
動作になります。

アクションでスクリプト実行を記録しておけば、ワンキーで全ての操作が可能です。

Photoshopをインストールすると、システム側に「TotalInkPreview.icc」というデバイスリンクプロファイルがインストールされます。CMYK画像に対し、これをカラールックアップ調整レイヤーで設定すると、CMYKの総量をグレースケールに置き換えて表示できます。

昔は調整レイヤーのメニュー内にあったんですが、ColorSyncユーティリティーやInDesign/Illustrator等が「このプロファイル壊れてる」と言ってくるためか、見えないところに移動された…ようで。
Macは
/Library/Application Support/Adobe/Color Profiles/TotalInkPreview.icc
Winは
C:\Program Files (x86)\Common Files\Adobe\Color Profiles\TotalInkPreview.icc
にあるはずです。OS毎に置かれている場所が違うので、素直に配布とかできないんでした。しょうがないのでjsxで書きました。

2025/05/08 更新

  • ヒストリーを汚さないようにしました。
  • 作成時にフォルダが閉じるようにしました。

jsxは以下から。
https://d.pr/f/2liDQt

コードはこちら

ClippingView.jsx
/*
/*
<javascriptresource>
<name>クリッピング表示</name>
<category>YPresets</category>
</javascriptresource>
*/

#target photoshop

if(app.documents.length){
app.activeDocument.suspendHistory("クリッピング表示","main()");
}
// ===========================================
// メイン処理
// ===========================================
function main() {
    if (toggleAllClippingViewGroups()) {return;}

    var colorMode = getDocumentColorMode();

    if (colorMode === "CMYK") {
        processCMYK();
    } else if (colorMode === "RGB") {
        processRGB();
    } else {
        alert("RGBまたはCMYKドキュメントで実行してください。");
    }
}

// ===========================================
// CMYKモード用の処理
// ===========================================
function processCMYK() {
    moveActiveLayerSelection(0);
    createGroup("TEMP");
    createColorLookupLayerWithICC(getICCPath(),"TAC2Gary");
    setLayerBlendRangeGrayFull([0,0,254,254],[0,0,255,255]);
    createThresholdLayer();
    setThresholdByTAC();
    applySolidFillEffect("CMYK",[100,50,0,0],"lighterColor");
    createClippingMask();
    createSolidColorLayer("CMYK",[0,100,100,0],"Highlight");
    setLayerBlendRangeGrayFull([0,0,255,255],[255,255,255,255]);
    moveActiveLayerSelection(0);
    ungroupSelectedLayers();
    selectLayerIDsFromTop(3);
    makeLayerGroup("Clipping_View_CMYK","orange");
}

// ===========================================
// RGBモード用の処理
// ===========================================
function processRGB() {
    moveActiveLayerSelection(0);
    createGroup("TEMP");
    createSolidColorLayer("RGB",[0,0,255],"Shadow");
    setLayerBlendRangeGrayFull([0,0,255,255],[0,0,0,0]);
    createSolidColorLayer("RGB",[255,0,0],"Highlight");
    setLayerBlendRangeGrayFull([0,0,255,255],[255,255,255,255]);
    moveActiveLayerSelection(0);
    ungroupSelectedLayers();
    selectLayerIDsFromTop(2);
    makeLayerGroup("Clipping_View_RGB","orange");
}

// ===========================================
// OSに応じてICCファイルのパスを返す
// ===========================================
function getICCPath() {
    var iccPath;

    if ($.os.toLowerCase().indexOf("mac") >= 0) {
        // Macの場合
        iccPath = "/Library/Application Support/Adobe/Color Profiles/TotalInkPreview.icc";
    } else {
        // Windowsの場合
        iccPath = "C:\\Program Files (x86)\\Common Files\\Adobe\\Color Profiles\\TotalInkPreview.icc";
    }

    var iccFile = new File(iccPath);
    if (!iccFile.exists) {
        alert(
            "ICCプロファイルが見つかりません:\n" +
            iccPath +
            "\n付近を探して、見つかったパスを script71行目あたりで書き換えてください"
        );
        throw new Error("ICCプロファイルが存在しません。");
    }

    return iccPath;
}

// ===========================================
// ドキュメント直下の「Clipping_View_」で始まるすべてのレイヤーグループを
// 最上位に移動し、表示/非表示をトグル
// ===========================================
function toggleAllClippingViewGroups() {
    if (app.documents.length === 0) return false;

    var doc = app.activeDocument;
    var targetPrefix = "Clipping_View_";
    var targetGroups = [];

    for (var i = 0; i < doc.layers.length; i++) {
        var layer = doc.layers[i];
        if (layer.typename === "LayerSet" && layer.name.indexOf(targetPrefix) === 0) {
            targetGroups.push(layer);
        }
    }

    if (targetGroups.length === 0) return false;

    for (var j = targetGroups.length - 1; j >= 0; j--) {
        var group = targetGroups[j];
        if (doc.layers[0] !== group) {
            group.move(doc.layers[0], ElementPlacement.PLACEBEFORE);
        }
    }
    for (var k = 0; k < targetGroups.length; k++) {
        targetGroups[k].visible = !targetGroups[k].visible;
    }

    return true;
}

// ===========================================
// 選択中の2階調化レイヤーの閾値を、総インキ量(TAC)入力から再設定する
// ===========================================
function setThresholdByTAC() {
    if (app.documents.length && app.activeDocument.activeLayer.kind === LayerKind.THRESHOLD) {
        var layer = app.activeDocument.activeLayer;

        var match = layer.name.match(/TAC値[::]\s*(\d+(?:\.\d+)?)%?/);
        var defaultValue = match ? match[1] : "350";

        var x = null;
        while (true) {
            var input = prompt("総インキ量を入力してください(1〜400%)", defaultValue);
            if (input === null) break;
            x = parseFloat(input);
            if (!isNaN(x) && x >= 1 && x <= 400) break;
            alert("1〜400 の数値を入力してください。");
        }

        if (x !== null) {
            var level = Math.round(255 * (1 - x / 400));

            var desc = new ActionDescriptor();
            var ref = new ActionReference();
            ref.putEnumerated(stringIDToTypeID("adjustmentLayer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
            desc.putReference(stringIDToTypeID("null"), ref);

            var toDesc = new ActionDescriptor();
            toDesc.putInteger(stringIDToTypeID("level"), level);
            desc.putObject(stringIDToTypeID("to"), stringIDToTypeID("thresholdClassEvent"), toDesc);

            executeAction(stringIDToTypeID("set"), desc, DialogModes.NO);

            layer.name = "TAC値:" + x + "%";
        }

    } else {
        alert("2階調化調整レイヤーを選択してください。");
    }
}

// ===========================================
// アクティブレイヤーの選択を指定位置に移動する
// 引数:0 = 最上部を選択、正数 = 上へ(前面側)選択移動、負数 = 下へ(背面側)選択移動
// ===========================================
function moveActiveLayerSelection(position) {
    var doc = app.activeDocument;
    var layers = getAllLayers(doc);
    var currentLayer = doc.activeLayer;
    var index = findLayerIndex(currentLayer, layers);

    if (index === -1) {
        alert("レイヤー位置を特定できません。");
        return;
    }

    if (position === 0) {
        doc.activeLayer = layers[0];
    } else {
        var targetIndex = index - position;
        if (targetIndex < 0) {
            targetIndex = 0;
        }
        if (targetIndex >= layers.length) {
            targetIndex = layers.length - 1;
        }
        doc.activeLayer = layers[targetIndex];
    }
}

// ===========================================
// 指定した名前と色でレイヤーグループを作成
// ===========================================
function makeLayerGroup(groupName, groupColorName) {
    var idMake = stringIDToTypeID("make");
    var desc = new ActionDescriptor();

    var refClass = new ActionReference();
    refClass.putClass(stringIDToTypeID("layerSection"));
    desc.putReference(stringIDToTypeID("null"), refClass);

    var refFrom = new ActionReference();
    refFrom.putEnumerated(
        stringIDToTypeID("layer"),
        stringIDToTypeID("ordinal"),
        stringIDToTypeID("targetEnum")
    );
    desc.putReference(stringIDToTypeID("from"), refFrom);

    var usingDesc = new ActionDescriptor();
    usingDesc.putString(stringIDToTypeID("name"), groupName);
    usingDesc.putEnumerated(
        stringIDToTypeID("color"),
        stringIDToTypeID("color"),
        stringIDToTypeID(groupColorName)
    );
    desc.putObject(stringIDToTypeID("using"), stringIDToTypeID("layerSection"), usingDesc);
    desc.putString(stringIDToTypeID("name"), groupName);

    executeAction(idMake, desc, DialogModes.NO);
}

// ===========================================
// 選択中のレイヤーグループを解除(ungroup)
// ===========================================
function ungroupSelectedLayers() {
    // var idUngroup = stringIDToTypeID("ungroupLayersEvent");
    // var desc = new ActionDescriptor();

    // var ref = new ActionReference();
    // ref.putEnumerated(
    //     stringIDToTypeID("layer"),
    //     stringIDToTypeID("ordinal"),
    //     stringIDToTypeID("targetEnum")
    // );
    // desc.putReference(stringIDToTypeID("null"), ref);

    // executeAction(idUngroup, desc, DialogModes.NO);

    var idungroupLayersEvent = stringIDToTypeID( "ungroupLayersEvent" );
    var desc308 = new ActionDescriptor();
    var idnull = stringIDToTypeID( "null" );
        var ref48 = new ActionReference();
        var idlayer = stringIDToTypeID( "layer" );
        var idordinal = stringIDToTypeID( "ordinal" );
        var idtargetEnum = stringIDToTypeID( "targetEnum" );
        ref48.putEnumerated( idlayer, idordinal, idtargetEnum );
    desc308.putReference( idnull, ref48 );
executeAction( idungroupLayersEvent, desc308, DialogModes.NO );
}

// ===========================================
// 全レイヤーリスト内でレイヤーのインデックスを見つける
// ===========================================
function findLayerIndex(targetLayer, layers) {
    for (var i = 0; i < layers.length; i++) {
        if (layers[i] === targetLayer) {
            return i;
        }
    }
    return -1;
}

// ===========================================
// 上から順に指定数の layerID を選択
// ===========================================
function selectLayerIDsFromTop(count) {
    var ids = getAllLayerIDs(app.activeDocument).slice(0, count);
    if (!(ids instanceof Array)) ids = [ids];

    var selectDesc = new ActionDescriptor();
    var selectRef = new ActionReference();

    for (var i = 0; i < ids.length; i++) {
        selectRef.putIdentifier(charIDToTypeID("Lyr "), ids[i]);
    }

    selectDesc.putReference(charIDToTypeID("null"), selectRef);
    selectDesc.putBoolean(charIDToTypeID("MkVs"), false);

    executeAction(charIDToTypeID("slct"), selectDesc, DialogModes.NO);
}

// ===========================================
// 単一レイヤーからlayerIDを取得
// ===========================================
function getLayerID(layer) {
    try {
        var ref = new ActionReference();
        ref.putIndex(charIDToTypeID("Lyr "), layer.itemIndex);
        var desc = executeActionGet(ref);
        return desc.getInteger(stringIDToTypeID("layerID"));
    } catch (e) {
        return -1;
    }
}

// ===========================================
// ドキュメント内の全レイヤーの layerID を上から順に返す
// ===========================================
function getAllLayerIDs(doc) {
    var ids = [];

    function collect(container) {
        for (var i = 0; i < container.layers.length; i++) {
            var layer = container.layers[i];
            if (layer.typename === "ArtLayer" || layer.typename === "LayerSet") {
                var id = getLayerID(layer);
                if (id !== -1) ids.push(id);
                if (layer.typename === "LayerSet") collect(layer);
            }
        }
    }

    collect(doc);
    return ids;
}

// ===========================================
// ドキュメント内の全レイヤーをフラットに取得する
// ===========================================
function getAllLayers(doc) {
    var layers = [];

    function collect(layerContainer) {
        for (var i = 0; i < layerContainer.layers.length; i++) {
            var layer = layerContainer.layers[i];
            if (layer.typename === "ArtLayer") {
                layers.push(layer);
            } else if (layer.typename === "LayerSet") {
                layers.push(layer);
                collect(layer);
            }
        }
    }

    collect(doc);
    return layers;
}

// ===========================================
// 指定した名前で新しいフォルダ(レイヤーグループ)を作成する
// ===========================================
function createGroup(name) {
    var doc = app.activeDocument;
    var group = doc.layerSets.add();
    group.name = name;
}

// ===========================================
// アクティブなレイヤーをクリッピングマスクにする
// ===========================================
function createClippingMask() {
    app.activeDocument.activeLayer.grouped = true;
}

// ===========================================
// 現在のドキュメントのカラーモードを判定して返す
// ("RGB" / "CMYK" / "Other" を返す)
// ===========================================
function getDocumentColorMode() {
    var mode = app.activeDocument.mode;
    if (mode == DocumentMode.RGB) {
        return "RGB";
    } else if (mode == DocumentMode.CMYK) {
        return "CMYK";
    } else {
        return "Other";
    }
}

// ===========================================
// 2階調化(Threshold)レイヤーを作成する
// ===========================================
function createThresholdLayer(level) {
    var idmake = stringIDToTypeID("make");
    var desc = new ActionDescriptor();

    var idnull = stringIDToTypeID("null");
    var ref = new ActionReference();
    var idadjustmentLayer = stringIDToTypeID("adjustmentLayer");
    ref.putClass(idadjustmentLayer);
    desc.putReference(idnull, ref);

    var idusing = stringIDToTypeID("using");
    var descUsing = new ActionDescriptor();

    var idtype = stringIDToTypeID("type");
    var descType = new ActionDescriptor();
    var idlevel = stringIDToTypeID("level");
    descType.putInteger(idlevel, (level !== undefined) ? level : 128);

    var idthresholdClassEvent = stringIDToTypeID("thresholdClassEvent");
    descUsing.putObject(idtype, idthresholdClassEvent, descType);

    desc.putObject(idusing, idadjustmentLayer, descUsing);

    executeAction(idmake, desc, DialogModes.NO);
}

// ===========================================
// Color Lookup調整レイヤーを作成し、ICCプロファイルを適用、レイヤー名を設定する
// ===========================================
function createColorLookupLayerWithICC(filePath, layerName) {
    try {
        var desc = new ActionDescriptor();
        var ref = new ActionReference();
        ref.putClass(stringIDToTypeID("adjustmentLayer"));
        desc.putReference(stringIDToTypeID("null"), ref);

        var descInner = new ActionDescriptor();
        descInner.putClass(stringIDToTypeID("type"), stringIDToTypeID("colorLookup"));
        desc.putObject(stringIDToTypeID("using"), stringIDToTypeID("adjustmentLayer"), descInner);

        executeAction(stringIDToTypeID("make"), desc, DialogModes.NO);

        var iccFile = new File(filePath);
        if (!iccFile.exists) {
            alert("指定されたICCファイルが存在しません: " + filePath);
            return;
        }

        var refSet = new ActionReference();
        refSet.putEnumerated(
            charIDToTypeID("AdjL"),
            charIDToTypeID("Ordn"),
            charIDToTypeID("Trgt")
        );

        var descSet = new ActionDescriptor();
        descSet.putReference(charIDToTypeID("null"), refSet);

        var descLookup = new ActionDescriptor();
        descLookup.putEnumerated(
            stringIDToTypeID("lookupType"),
            stringIDToTypeID("colorLookupType"),
            stringIDToTypeID("deviceLinkProfile")
        );
        descLookup.putString(charIDToTypeID("Nm  "), iccFile.name);

        iccFile.open("r");
        iccFile.encoding = "BINARY";
        var binData = iccFile.read();
        iccFile.close();

        descLookup.putData(stringIDToTypeID("profile"), binData);

        descSet.putObject(
            charIDToTypeID("T   "),
            stringIDToTypeID("colorLookup"),
            descLookup
        );

        executeAction(charIDToTypeID("setd"), descSet, DialogModes.NO);

        if (layerName) {
            app.activeDocument.activeLayer.name = layerName;
        }

    } catch (e) {
        alert("エラー: " + e);
    }
}

// ===========================================
// 選択レイヤーのブレンド条件(グレー)を設定する
// 引数: srcRangeArray, destRangeArray
// - srcRangeArray = [srcBlackMin, srcBlackMax, srcWhiteMin, srcWhiteMax]
// - destRangeArray = [destBlackMin, destBlackMax, destWhiteMin, destWhiteMax]
// ===========================================
function setLayerBlendRangeGrayFull(srcRangeArray, destRangeArray) {
    if (!srcRangeArray || !destRangeArray || srcRangeArray.length !== 4 || destRangeArray.length !== 4) {
        throw new Error("srcRangeArrayとdestRangeArrayにはそれぞれ4つの数値を指定してください。");
    }

    var desc = new ActionDescriptor();
    var ref = new ActionReference();
    ref.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
    desc.putReference(stringIDToTypeID("null"), ref);

    var layerDesc = new ActionDescriptor();
    var blendRangeList = new ActionList();

    var grayChannelDesc = new ActionDescriptor();
    var channelRef = new ActionReference();
    channelRef.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("gray"));
    grayChannelDesc.putReference(stringIDToTypeID("channel"), channelRef);

    // 現在のレイヤー
    grayChannelDesc.putInteger(stringIDToTypeID("srcBlackMin"), srcRangeArray[0]);
    grayChannelDesc.putInteger(stringIDToTypeID("srcBlackMax"), srcRangeArray[1]);
    grayChannelDesc.putInteger(stringIDToTypeID("srcWhiteMin"), srcRangeArray[2]);
    grayChannelDesc.putInteger(stringIDToTypeID("srcWhiteMax"), srcRangeArray[3]);

    // 下になっているレイヤー
    grayChannelDesc.putInteger(stringIDToTypeID("destBlackMin"), destRangeArray[0]);
    grayChannelDesc.putInteger(stringIDToTypeID("destBlackMax"), destRangeArray[1]);
    grayChannelDesc.putInteger(stringIDToTypeID("destWhiteMin"), destRangeArray[2]);
    grayChannelDesc.putInteger(stringIDToTypeID("destWhiteMax"), destRangeArray[3]);

    blendRangeList.putObject(stringIDToTypeID("blendRange"), grayChannelDesc);
    layerDesc.putList(stringIDToTypeID("blendRange"), blendRangeList);

    desc.putObject(stringIDToTypeID("to"), stringIDToTypeID("layer"), layerDesc);

    executeAction(stringIDToTypeID("set"), desc, DialogModes.NO);
}

// ===========================================
// アクティブレイヤーに単色塗りレイヤースタイルを適用する
// 引数:
// - colorMode: "RGB" または "CMYK"
// - colorArray: [R,G,B] または [C,M,Y,K]
// - blendMode: ブレンドモード(省略時は"normal")
// ===========================================
function applySolidFillEffect(colorMode, colorArray, blendMode) {
    if (!colorMode || !colorArray || !(colorArray instanceof Array)) {
        throw new Error("colorMode と colorArray を正しく指定してください。");
    }

    var desc = new ActionDescriptor();
    var ref = new ActionReference();
    ref.putProperty(stringIDToTypeID("property"), stringIDToTypeID("layerEffects"));
    ref.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
    desc.putReference(stringIDToTypeID("null"), ref);

    var layerEffectsDesc = new ActionDescriptor();
    layerEffectsDesc.putUnitDouble(stringIDToTypeID("scale"), stringIDToTypeID("percentUnit"), 100.0);

    var solidFillDesc = new ActionDescriptor();
    solidFillDesc.putBoolean(stringIDToTypeID("enabled"), true);
    solidFillDesc.putBoolean(stringIDToTypeID("present"), true);
    solidFillDesc.putBoolean(stringIDToTypeID("showInDialog"), true);

    solidFillDesc.putEnumerated(
        stringIDToTypeID("mode"),
        stringIDToTypeID("blendMode"),
        stringIDToTypeID(blendMode || "normal")
    );

    // buildColorDescriptor関数を使用
    var colorResult = buildColorDescriptor(colorMode, colorArray);
    solidFillDesc.putObject(stringIDToTypeID("color"), stringIDToTypeID(colorResult.colorType), colorResult.colorDesc);

    solidFillDesc.putUnitDouble(stringIDToTypeID("opacity"), stringIDToTypeID("percentUnit"), 100.0);

    layerEffectsDesc.putObject(stringIDToTypeID("solidFill"), stringIDToTypeID("solidFill"), solidFillDesc);
    desc.putObject(stringIDToTypeID("to"), stringIDToTypeID("layerEffects"), layerEffectsDesc);

    executeAction(stringIDToTypeID("set"), desc, DialogModes.NO);
}

// ===========================================
// ベタ塗りレイヤー(Solid Color Fill Layer)を作成する(RGB/CMYK対応)
// 引数:
// - colorMode: "RGB" または "CMYK"
// - colorArray: [R,G,B] または [C,M,Y,K]
// - name: レイヤー名(省略可)
// ===========================================
function createSolidColorLayer(colorMode, colorArray, name) {
    if (!colorMode || !colorArray || !(colorArray instanceof Array)) {
        throw new Error("colorMode と colorArray を正しく指定してください。");
    }

    var desc = new ActionDescriptor();
    var ref = new ActionReference();
    ref.putClass(stringIDToTypeID("contentLayer"));
    desc.putReference(stringIDToTypeID("null"), ref);

    var usingDesc = new ActionDescriptor();
    var typeDesc = new ActionDescriptor();

    // buildColorDescriptor関数を使用
    var colorResult = buildColorDescriptor(colorMode, colorArray);
    typeDesc.putObject(stringIDToTypeID("color"), stringIDToTypeID(colorResult.colorType), colorResult.colorDesc);

    usingDesc.putObject(stringIDToTypeID("type"), stringIDToTypeID("solidColorLayer"), typeDesc);
    desc.putObject(stringIDToTypeID("using"), stringIDToTypeID("contentLayer"), usingDesc);

    executeAction(stringIDToTypeID("make"), desc, DialogModes.NO);

    if (name) {
        app.activeDocument.activeLayer.name = name;
    }
}

// ===========================================
// colorModeとcolorArrayに応じたColor Descriptorを生成する
// 戻り値: { colorDesc: ActionDescriptor, colorType: "RGBColor" or "CMYKColorClass" }
// ===========================================
function buildColorDescriptor(colorMode, colorArray) {
    var colorDesc = new ActionDescriptor();

    if (colorMode === "RGB") {
        colorDesc.putDouble(stringIDToTypeID("red"),   colorArray[0] || 0);
        colorDesc.putDouble(stringIDToTypeID("green"), colorArray[1] || 0);
        colorDesc.putDouble(stringIDToTypeID("blue"),  colorArray[2] || 0);
        return { colorDesc: colorDesc, colorType: "RGBColor" };
    } else if (colorMode === "CMYK") {
        colorDesc.putDouble(stringIDToTypeID("cyan"),    colorArray[0] || 0);
        colorDesc.putDouble(stringIDToTypeID("magenta"), colorArray[1] || 0);
        colorDesc.putDouble(stringIDToTypeID("yellowColor"), colorArray[2] || 0);
        colorDesc.putDouble(stringIDToTypeID("black"),   colorArray[3] || 0);
        return { colorDesc: colorDesc, colorType: "CMYKColorClass" };
    } else {
        throw new Error("colorModeには 'RGB' または 'CMYK' を指定してください。");
    }
}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?