Markdown Image Renderer for AI Studio
通称:スタ画、英名「MIR」
目的:AI Studioのプロンプト画面でMarkdown形式の画像リンクを実際の画像として表示します
使用手順:①Tampermonkeyのプラグインを インストール
②「MIRインストール」を押して、スクリプトインストールを行う(またはTampermonkeyで、以下のコードを保存)
③Google Studio AIを起動して画像出る系のプロンプトでプレイ
うまくいくと以下の様にでます

詳しくは下部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | // ==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");
// =========================================
// IndexedDB(image-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に追加しましょう。
- Chromeウェブストアにアクセス: こちらのリンクからTampermonkeyのページを開きます。
- 「Chromeに追加」をクリック: 右上にある青い「Chromeに追加」ボタンをクリックします。
- 拡張機能を追加: 確認画面が表示されたら「拡張機能を追加」をクリックします。
これだけでインストールは完了です。 ブラウザの右上に、黒い四角に丸が2つ描かれたTampermonkeyのアイコンが表示されていれば成功です。
3. スクリプトを探してインストールしてみよう
Tampermonkeyの真価は、便利なユーザースクリプトを追加することにあります。スクリプトは、専門のサイトで探すのが一般的です。
- Greasy Fork (グリーシーフォーク): 最も有名で、たくさんのスクリプトが公開されているサイトです。
Greasy Forkでスクリプトをインストールする手順:
- サイトにアクセス: Greasy Forkにアクセスします。
- スクリプトを探す: サイト内検索で、自分が使っているWebサイト名(例:「YouTube」)や、やりたいこと(例:「広告非表示」)などで検索します。
- インストール: 気に入ったスクリプトが見つかったら、そのページを開き、「スクリプトをインストール」という緑色のボタンをクリックします。
- 確認画面: Tampermonkeyの画面に切り替わり、スクリプトの内容が表示されます。内容を確認し、左上にある「インストール」ボタンをクリックすれば完了です。
4. 開発者モードの使い方(自作・改造に挑戦!)
ここからは、自分でスクリプトを作成したり、既存のスクリプトを改造したりするための「開発者モード」の使い方です。
なぜ開発者モードが必要?
最近のChromeの仕様変更により、Tampermonkeyでスクリプトを動作させるには「開発者モード」を有効にすることが必須となりました。 これはセキュリティ上の理由によるものです。
開発者モードを有効にする手順
- 拡張機能の管理画面を開く:
- Chromeの右上にあるパズルのピースのような「拡張機能」アイコンをクリックします。
- メニューの下部にある「拡張機能を管理」を選択します。
- 開発者モードをONにする:
- 拡張機能一覧ページの右上にある「デベロッパー モード」(または「開発者モード」)のスイッチをクリックしてONにします。
- ユーザースクリプトをONにする:
- Tampermonkeyの詳細を押す

- 「ユーザースクリプトを許可する」をONにする

- 「URLのアクセスを許可する」をONにする→上部にあった「MIRインストール」が可能になります

- Tampermonkeyの詳細を押す
新しいスクリプトを作成する方法
- Tampermonkeyアイコンをクリック: ブラウザ右上のTampermonkeyアイコンをクリックします。
- 「新規スクリプトを追加」を選択: 表示されたメニューから「新規スクリプトを追加」をクリックします。
- エディタ画面が開く: 新しいタブで、スクリプトを編集するためのエディタ画面が開きます。
- コードを記述: 最初から入っているテンプレートに沿って、JavaScriptのコードを書いていきます。
// @name: スクリプトの名前// @match: スクリプトをどのWebサイトで実行するかを指定します。 例えば、https://www.google.com/*と書くと、Googleのページでのみ実行されます。// Your code here...の下に、実行したい処理をJavaScriptで記述します。
- 保存: コードを書き終えたら、エディタ画面の「ファイル」メニューから「保存」をクリックします。
これで、あなたの作成したスクリプトが有効になり、指定したサイトを開くと自動的に実行されるようになります。もしうまく動かない場合は、開発者ツールのコンソールでエラーを確認してみましょう。
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