Markdown Image Renderer for AI Studio

通称:スタ画、英名「MIR」

目的:AI Studioのプロンプト画面でMarkdown形式の画像リンクを実際の画像として表示します

使用手順:①Tampermonkeyのプラグインを インストール
②「MIRインストール」を押して、スクリプトインストールを行う(またはTampermonkeyで、以下のコードを保存)
Google Studio AIを起動して画像出る系のプロンプトでプレイ

うまくいくと以下の様にでます
C

詳しくは下部

// ==UserScript==
// @name         Markdown Image Renderer for AI Studio
// @namespace    https://rentry.co/3bnuvgwu
// @license      MIT
// @version      5.2
// @description  AI StudioでMarkdown画像が表示できるようになります:character記法、クリック時に別ウィンドウ対応)
// @author       ForeverPWA
// @match        *://aistudio.google.com/*
// @grant        GM_xmlhttpRequest
// @connect      *
// ==/UserScript==

(function () {
    'use strict';

    const LOG_PREFIX = "🖼️ MIR:";
    console.log(LOG_PREFIX, "v5.2 started");

    // =========================================
    // IndexedDBimage-manager.user.jsと同じDB
    // =========================================
    const DB_NAME = 'ImageManagerDB';
    const DB_VERSION = 1;

    // キャッシュ: `:character/キャラ名/衣装名/表情名`  { data, mimeType }
    let imageCache = {};
    let cacheReady = false;

    function openDB() {
        return new Promise((resolve, reject) => {
            const req = indexedDB.open(DB_NAME, DB_VERSION);
            req.onerror = () => reject(req.error);
            req.onsuccess = () => resolve(req.result);
        });
    }

    // IndexedDBから全データを読み込んで :character/... 形式でキャッシュ
    async function loadImageCache() {
        try {
            const db = await openDB();

            // キャラクター一覧
            const characters = await getAll(db, 'characters');
            // 衣装一覧
            const outfits = await getAll(db, 'outfits');
            // 画像一覧
            const images = await getAll(db, 'characterImages');

            db.close();

            // キャラクターIDマップ
            const charMap = {};
            characters.forEach(c => { charMap[c.id] = c.name; });

            // 衣装IDマップ (outfitId  { charName, outfitName })
            const outfitMap = {};
            outfits.forEach(o => {
                outfitMap[o.id] = {
                    charName: charMap[o.characterId] || 'unknown',
                    outfitName: o.name
                };
            });

            // 画像をキャッシュに登録
            images.forEach(img => {
                const outfit = outfitMap[img.outfitId];
                if (outfit) {
                    const key = `:character/${outfit.charName}/${outfit.outfitName}/${img.name}`;
                    imageCache[key] = {
                        data: img.data,  // base64文字列
                        mimeType: img.mimeType
                    };
                }
            });

            cacheReady = true;
            console.log(LOG_PREFIX, `Loaded ${images.length} images, cache keys:`, Object.keys(imageCache).slice(0, 5));
        } catch (e) {
            console.warn(LOG_PREFIX, "IndexedDB error:", e);
            cacheReady = true;
        }
    }

    function getAll(db, storeName) {
        return new Promise((resolve, reject) => {
            const tx = db.transaction(storeName, 'readonly');
            const store = tx.objectStore(storeName);
            const req = store.getAll();
            req.onsuccess = () => resolve(req.result || []);
            req.onerror = () => reject(req.error);
        });
    }

    // =========================================
    // URL画像取得(従来通り)
    // =========================================
    function fetchImageAsBlob(url, callback) {
        if (url.startsWith(window.location.origin) || url.startsWith('/')) {
            fetch(url)
                .then(r => r.ok ? r.blob() : Promise.reject())
                .then(callback)
                .catch(() => callback(null));
            return;
        }
        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            responseType: 'blob',
            onload: (r) => callback(r.status >= 200 && r.status < 300 ? r.response : null),
            onerror: () => callback(null)
        });
    }

    // =========================================
    // pre要素処理
    // =========================================
    function processPreElement(pre) {
        if (pre.dataset.mir) return;

        const text = pre.textContent || '';
        const match = text.match(/!\[([^\]]*)\]\(([^)]+)\)/);
        if (!match) return;

        const [, alt, src] = match;
        pre.dataset.mir = '1';

        // :character/パスの場合  キャッシュから取得
        if (src.startsWith(':character/')) {
            if (!cacheReady) return;

            // 拡張子なしで検索
            const pathNoExt = src.replace(/\.[^/.]+$/, '');
            const imgData = imageCache[pathNoExt] || imageCache[src];

            if (imgData) {
                console.log(LOG_PREFIX, `✓ Found: ${src}`);

                const img = document.createElement('img');
                // data は既にbase64文字列
                img.src = imgData.data;
                img.alt = alt || src;
                img.style.cssText = "max-width:100%;height:auto;display:block;margin:10px 0;border-radius:8px;cursor:pointer;";
                img.title = src;
                img.onclick = () => {
                    // base64の場合はBlobURLに変換して開く
                    if (imgData.data.startsWith('data:')) {
                        try {
                            const [header, base64Data] = imgData.data.split(',');
                            const mimeType = header.match(/data:([^;]+)/)?.[1] || 'image/png';
                            const byteCharacters = atob(base64Data);
                            const byteNumbers = new Array(byteCharacters.length);
                            for (let i = 0; i < byteCharacters.length; i++) {
                                byteNumbers[i] = byteCharacters.charCodeAt(i);
                            }
                            const byteArray = new Uint8Array(byteNumbers);
                            const blob = new Blob([byteArray], { type: mimeType });
                            const blobUrl = URL.createObjectURL(blob);
                            window.open(blobUrl, '_blank');
                        } catch (err) {
                            console.error(LOG_PREFIX, '画像を開けませんでした:', err);
                        }
                    } else {
                        window.open(imgData.data, '_blank');
                    }
                };

                pre.style.display = 'none';
                pre.parentNode?.insertBefore(img, pre.nextSibling);
            } else {
                console.log(LOG_PREFIX, `✗ Not found: ${src} (keys: ${Object.keys(imageCache).length})`);
            }
            return;
        }

        // URL画像の場合  従来通りfetch
        console.log(LOG_PREFIX, `Fetch: ${src}`);

        const img = document.createElement('img');
        img.alt = alt || 'Loading...';
        img.style.cssText = "max-width:100%;height:auto;display:block;margin:10px 0;border-radius:8px;background:#f0f0f0;min-height:50px;cursor:pointer;";
        img.title = src;
        img.onclick = () => window.open(src, '_blank');

        pre.style.display = 'none';
        pre.parentNode?.insertBefore(img, pre.nextSibling);

        fetchImageAsBlob(src, (blob) => {
            if (blob) {
                const url = URL.createObjectURL(blob);
                img.src = url;
                img.alt = alt;
                img.onload = () => URL.revokeObjectURL(url);
            } else {
                img.alt = `[Failed] ${alt}`;
                img.style.border = "2px dashed #d93025";
            }
        });
    }

    function scan() {
        document.querySelectorAll('ms-chat-turn pre, .prompt-textarea pre').forEach(processPreElement);
    }

    // =========================================
    // 初期化
    // =========================================
    let timer = null;
    new MutationObserver(() => {
        clearTimeout(timer);
        timer = setTimeout(scan, 300);
    }).observe(document.body, { childList: true, subtree: true });

    loadImageCache().then(() => {
        setTimeout(scan, 500);
    });

})();

1. Tampermonkeyって何?

Tampermonkeyは、一言でいうと「Webサイトを自分好みに改造できるGoogle Chromeの拡張機能」です。

例えば、以下のようなことができます。

  • 見た目を変更: 文字の大きさを変えたり、不要な広告を非表示にしたりできます。
  • 機能を追加: YouTubeにダウンロードボタンを追加したり、SNSサイトに便利な機能を追加したりできます。
  • 作業を自動化: 定型的なフォーム入力を自動化したりできます。

これらは「ユーザースクリプト」と呼ばれる小さなプログラムをTampermonkeyに追加することで実現します。自分でプログラムを書けなくても、他の人が作成して公開しているスクリプトをインストールするだけで、様々な恩恵を受けられます。

2. Tampermonkeyのインストール(超かんたん!)

まずは、TampermonkeyをGoogle Chromeに追加しましょう。

  1. Chromeウェブストアにアクセス: こちらのリンクからTampermonkeyのページを開きます。
  2. 「Chromeに追加」をクリック: 右上にある青い「Chromeに追加」ボタンをクリックします。
  3. 拡張機能を追加: 確認画面が表示されたら「拡張機能を追加」をクリックします。

これだけでインストールは完了です。 ブラウザの右上に、黒い四角に丸が2つ描かれたTampermonkeyのアイコンが表示されていれば成功です。

3. スクリプトを探してインストールしてみよう

Tampermonkeyの真価は、便利なユーザースクリプトを追加することにあります。スクリプトは、専門のサイトで探すのが一般的です。

  • Greasy Fork (グリーシーフォーク): 最も有名で、たくさんのスクリプトが公開されているサイトです。

Greasy Forkでスクリプトをインストールする手順:

  1. サイトにアクセス: Greasy Forkにアクセスします。
  2. スクリプトを探す: サイト内検索で、自分が使っているWebサイト名(例:「YouTube」)や、やりたいこと(例:「広告非表示」)などで検索します。
  3. インストール: 気に入ったスクリプトが見つかったら、そのページを開き、「スクリプトをインストール」という緑色のボタンをクリックします。
  4. 確認画面: Tampermonkeyの画面に切り替わり、スクリプトの内容が表示されます。内容を確認し、左上にある「インストール」ボタンをクリックすれば完了です。

4. 開発者モードの使い方(自作・改造に挑戦!)

ここからは、自分でスクリプトを作成したり、既存のスクリプトを改造したりするための「開発者モード」の使い方です。

なぜ開発者モードが必要?

最近のChromeの仕様変更により、Tampermonkeyでスクリプトを動作させるには「開発者モード」を有効にすることが必須となりました。 これはセキュリティ上の理由によるものです。

開発者モードを有効にする手順

  1. 拡張機能の管理画面を開く:
    • Chromeの右上にあるパズルのピースのような「拡張機能」アイコンをクリックします。
    • メニューの下部にある「拡張機能を管理」を選択します。
  2. 開発者モードをONにする:
    • 拡張機能一覧ページの右上にある「デベロッパー モード」(または「開発者モード」)のスイッチをクリックしてONにします。
  3. ユーザースクリプトをONにする:
    • Tampermonkeyの詳細を押す
      C
    • 「ユーザースクリプトを許可する」をONにする
      C
    • 「URLのアクセスを許可する」をONにする→上部にあった「MIRインストール」が可能になります
      C

新しいスクリプトを作成する方法

  1. Tampermonkeyアイコンをクリック: ブラウザ右上のTampermonkeyアイコンをクリックします。
  2. 「新規スクリプトを追加」を選択: 表示されたメニューから「新規スクリプトを追加」をクリックします。
  3. エディタ画面が開く: 新しいタブで、スクリプトを編集するためのエディタ画面が開きます。
  4. コードを記述: 最初から入っているテンプレートに沿って、JavaScriptのコードを書いていきます。
    • // @name: スクリプトの名前
    • // @match: スクリプトをどのWebサイトで実行するかを指定します。 例えば、https://www.google.com/*と書くと、Googleのページでのみ実行されます。
    • // Your code here... の下に、実行したい処理をJavaScriptで記述します。
  5. 保存: コードを書き終えたら、エディタ画面の「ファイル」メニューから「保存」をクリックします。

これで、あなたの作成したスクリプトが有効になり、指定したサイトを開くと自動的に実行されるようになります。もしうまく動かない場合は、開発者ツールのコンソールでエラーを確認してみましょう。

5. 注意点

  • AIにスクリプトの内容を確認する: コードをAIに聞いて安全か確認してみよう
  • 信頼できるスクリプトを: スクリプトは便利な反面、悪意のあるコードが含まれている可能性もゼロではありません。出所が不明なスクリプトや、内容がよく分からないスクリプトのインストールは避けましょう。
  • 定期的なアップデート: Tampermonkeyやインストールしたスクリプトは、自動で更新される設定にしておくと、セキュリティ面でも安心です。

Tampermonkeyを使いこなせば、普段のWebブラウジングが格段に快適になります。ぜひ、色々なスクリプトを試したり、簡単なスクリプトの自作に挑戦してみてください。

うまくいかない!

ブラウザの再起動か、「画像は()括弧でくくって」とチャットで打つと直る事もあり

こちらの画像は以下の提供でお送りしました。ありがとうございます。
個人的シコキャラ置き場様
https://rentry.co/ar6e9t2n

関連ツール

スタ画ライブラリ

当該ツールで表示する画像を管理できます
https://rentry.co/78hcq7c7/

その他のツール

URL自動デコーダー

通称:自デコ
https://rentry.co/3hb6piip

AI Studio以外のAIモデルでも画像出力したい!(Gemini, ChatGPT, Grok用)

Markdown Image Renderer for Gemini, ChatGPT, Grok
通常:MIRG
https://rentry.co/kuzpbema

AI Studioで自動音声読み上げ(VOICEVOX)が欲しい!

通称:スタ自ボ
https://rentry.co/x9fw82o3

Wikiのプロンプトまとめ等をみながら、プロンプトだけを即コピーしたい人用に作成しました

通称:即コピ
https://rentry.co/8772bcnh

Edit

Pub: 18 Oct 2025 18:23 UTC

Edit: 22 Jan 2026 03:44 UTC

Views: 1760