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 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 | // ==UserScript==
// @name Markdown Image Renderer for Google AI Studio
// @namespace https://rentry.co/3bnuvgwu
// @license MIT
// @version 5.7
// @description AI StudioでMarkdown画像を安定表示する拡張版。localhost画像の取得を改善。
// @author ForeverPWA
// @match *://aistudio.google.com/*
// @grant GM_xmlhttpRequest
// @connect *
// ==/UserScript==
(function () {
'use strict';
const LOG_PREFIX = "MIR:";
console.log(LOG_PREFIX, "v5.7 started");
const DB_NAME = 'ImageManagerDB';
const DB_VERSION = 1;
let imageCache = {};
let cacheReady = false;
const blobUrlCache = {};
let previewWindow = null;
function openInPreview(url) {
try {
if (previewWindow && !previewWindow.closed) {
previewWindow.location.replace(url);
previewWindow.focus();
} else {
previewWindow = window.open(url, 'imagePreviewWindow');
}
} catch (e) {
previewWindow = window.open(url, 'imagePreviewWindow');
}
}
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);
});
}
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();
const charMap = {};
characters.forEach(c => { charMap[c.id] = c.name; });
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,
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);
});
}
function getBlobUrlFromDataUrl(cacheKey, dataUrl) {
if (blobUrlCache[cacheKey]) return blobUrlCache[cacheKey];
const [header, base64Data] = dataUrl.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);
blobUrlCache[cacheKey] = blobUrl;
return blobUrl;
}
function isLocalhostUrl(url) {
try {
const parsed = new URL(url, window.location.href);
return parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1';
} catch (e) {
return false;
}
}
function isSameOriginUrl(url) {
try {
const parsed = new URL(url, window.location.href);
return parsed.origin === window.location.origin;
} catch (e) {
return url.startsWith('/');
}
}
function fetchViaWindowFetch(url, callback) {
fetch(url, { mode: 'cors', credentials: 'omit' })
.then(r => {
if (!r.ok) {
throw new Error(`HTTP ${r.status}`);
}
return r.blob();
})
.then(callback)
.catch((error) => {
console.warn(LOG_PREFIX, `fetch failed for ${url}:`, error);
callback(null);
});
}
function fetchImageAsBlob(url, callback) {
if (isSameOriginUrl(url) || url.startsWith('/')) {
fetchViaWindowFetch(url, callback);
return;
}
if (isLocalhostUrl(url)) {
console.log(LOG_PREFIX, `localhost fetch path: ${url}`);
fetchViaWindowFetch(url, (blob) => {
if (blob) {
callback(blob);
return;
}
GM_xmlhttpRequest({
method: 'GET',
url: url,
responseType: 'blob',
onload: (r) => {
if (r.status >= 200 && r.status < 300) {
callback(r.response);
} else {
console.warn(LOG_PREFIX, `GM_xmlhttpRequest failed for ${url}: HTTP ${r.status}`);
callback(null);
}
},
onerror: (error) => {
console.warn(LOG_PREFIX, `GM_xmlhttpRequest network error for ${url}:`, error);
callback(null);
}
});
});
return;
}
GM_xmlhttpRequest({
method: 'GET',
url: url,
responseType: 'blob',
onload: (r) => {
if (r.status >= 200 && r.status < 300) {
callback(r.response);
} else {
console.warn(LOG_PREFIX, `GM_xmlhttpRequest failed for ${url}: HTTP ${r.status}`);
callback(null);
}
},
onerror: (error) => {
console.warn(LOG_PREFIX, `GM_xmlhttpRequest network error for ${url}:`, error);
callback(null);
}
});
}
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';
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');
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 = (e) => {
e.preventDefault();
try {
if (imgData.data.startsWith('data:')) {
const blobUrl = getBlobUrlFromDataUrl(src, imgData.data);
openInPreview(blobUrl);
} else {
openInPreview(imgData.data);
}
} catch (err) {
console.error(LOG_PREFIX, 'preview open failed:', err);
}
};
pre.style.display = 'none';
pre.parentNode?.insertBefore(img, pre.nextSibling);
} else {
console.log(LOG_PREFIX, `Not found: ${src} (keys: ${Object.keys(imageCache).length})`);
}
return;
}
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;
let previewSrc = src;
img.onclick = (e) => {
e.preventDefault();
openInPreview(previewSrc);
};
pre.style.display = 'none';
pre.parentNode?.insertBefore(img, pre.nextSibling);
fetchImageAsBlob(src, (blob) => {
if (blob) {
const objectUrl = URL.createObjectURL(blob);
previewSrc = objectUrl;
img.src = objectUrl;
img.alt = alt;
} else {
img.alt = `[Failed] ${alt}`;
img.style.border = "2px dashed #d93025";
console.warn(LOG_PREFIX, `Image load failed: ${src}`);
}
});
}
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