画像の保存機能を追加

This commit is contained in:
ry.yamafuji 2025-06-07 22:21:18 +09:00
parent 78aaf9edeb
commit 325081773b
4 changed files with 69 additions and 14 deletions

View File

@ -1,7 +1,7 @@
# imagemarkpengent
![alt text](readme/images/top.png)
![alt text](./readme/images/top.png)
This is the README for your extension "imagemarkpengent". After writing up a brief description, we recommend including the following sections.

View File

@ -32,14 +32,16 @@ VSCode拡張機能「ImageMarkPengent」の実装タスク一覧です。
- [x] 対象拡張子を `.png` / `.jpg` / `.jpeg` に限定
### 2. WebViewで画像表示
- [ ] コマンド実行時にWebViewパネルを開く
- [ ] 画像ファイルを `<img>` または `<canvas>` で表示
- [ ] 画像の上に描き込み可能な `canvas` を重ねる
- [x] コマンド実行時にWebViewパネルを開く
- [x] 画像ファイルを `<img>` または `<canvas>` で表示
- [x] 画像の上に描き込み可能な `canvas` を重ねる
### 3. 赤丸マーク描画機能
- [ ] WebView内でJavaScriptにより `<canvas>` へ赤丸を描画
- [ ] クリック座標を取得し、赤丸を描画・保持
- [ ] 複数マークの描画・再描画に対応
- [x] WebView内でJavaScriptにより `<canvas>` へ赤丸を描画
- [x] クリック座標を取得し、赤丸を描画・保持
- [x] 複数マークの描画・再描画に対応
### 4. 加工画像の保存機能
- [ ] WebViewに「保存」ボタンを設置
@ -47,10 +49,3 @@ VSCode拡張機能「ImageMarkPengent」の実装タスク一覧です。
- [ ] `vscode.postMessage()` で拡張機能側に送信
- [ ] 拡張機能側でファイル保存(別名保存も対応)
---
## 今後の拡張案(任意)
- 番号付きマーカーや矢印の追加
- マークの色やサイズ変更
- Undo/Redo機能
- 他画像フォーマット対応

View File

@ -37,6 +37,29 @@ export function activate(context: vscode.ExtensionContext) {
const imageSrc = panel.webview.asWebviewUri(uri);
panel.webview.html = getWebviewContent(imageSrc.toString());
// WebViewからのメッセージ受信保存処理
panel.webview.onDidReceiveMessage(async (message) => {
if (message.type === 'save-image' && message.dataUrl) {
// ファイル保存ダイアログを表示
const uriSave = await vscode.window.showSaveDialog({
filters: { 'PNG Images': ['png'] },
saveLabel: '画像として保存'
});
if (!uriSave) return;
// dataUrlからbase64部分を抽出
const base64 = message.dataUrl.replace(/^data:image\/png;base64,/, '');
const buffer = Buffer.from(base64, 'base64');
const fs = require('fs');
fs.writeFile(uriSave.fsPath, buffer, (err: any) => {
if (err) {
vscode.window.showErrorMessage('画像の保存に失敗しました: ' + err.message);
} else {
vscode.window.showInformationMessage('画像を保存しました: ' + uriSave.fsPath);
}
});
}
});
});
context.subscriptions.push(disposable);

View File

@ -30,6 +30,7 @@ export function getWebviewContent(imageSrc: string): string {
<button id="moveBtn" class="active-btn"></button>
<button id="selectBtn"></button>
<button id="markBtn">(Ctr+)</button>
<button id="saveBtn"></button>
<label>:
<select id="lineWidth">
<option value="2">2px</option>
@ -54,6 +55,7 @@ export function getWebviewContent(imageSrc: string): string {
const markBtn = document.getElementById('markBtn');
const lineWidthSelect = document.getElementById('lineWidth');
const colorPicker = document.getElementById('colorPicker');
const saveBtn = document.getElementById('saveBtn');
// 状態
let mode = 'move'; // 'move' or 'mark' or 'select'
@ -115,6 +117,24 @@ export function getWebviewContent(imageSrc: string): string {
if (changed) pushUndo();
draw();
};
saveBtn.onclick = () => {
// 元画像サイズのオフスクリーンcanvasで保存
const offCanvas = document.createElement('canvas');
offCanvas.width = img.width;
offCanvas.height = img.height;
const offCtx = offCanvas.getContext('2d');
offCtx.clearRect(0, 0, offCanvas.width, offCanvas.height);
offCtx.drawImage(img, 0, 0);
// マークを描画(ズーム・オフセットなし)
for (const mark of marks) {
drawEllipseRaw(offCtx, mark.x1, mark.y1, mark.x2, mark.y2, mark.color, mark.lineWidth);
}
const dataUrl = offCanvas.toDataURL('image/png');
if (window.acquireVsCodeApi) {
const vscode = window.acquireVsCodeApi();
vscode.postMessage({ type: 'save-image', dataUrl });
}
};
function setMode(newMode) {
mode = newMode;
@ -462,6 +482,23 @@ export function getWebviewContent(imageSrc: string): string {
return ((px - cx) ** 2) / (rx ** 2) + ((py - cy) ** 2) / (ry ** 2) <= 1;
}
// オフスクリーン用: scale/offsetなしで楕円を描画
function drawEllipseRaw(ctx, x1, y1, x2, y2, color, lineWidth, dashed = false) {
ctx.save();
ctx.beginPath();
const cx = (x1 + x2) / 2;
const cy = (y1 + y2) / 2;
const rx = Math.abs(x2 - x1) / 2;
const ry = Math.abs(y2 - y1) / 2;
ctx.ellipse(cx, cy, rx, ry, 0, 0, 2 * Math.PI);
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
if (dashed) ctx.setLineDash([6, 6]);
ctx.stroke();
ctx.setLineDash([]);
ctx.restore();
}
// 初期リサイズ
resizeCanvas();
// 最初の状態をundoStackにpush