画像の保存機能を追加
This commit is contained in:
parent
78aaf9edeb
commit
325081773b
@ -1,7 +1,7 @@
|
|||||||
# imagemarkpengent
|
# imagemarkpengent
|
||||||
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
This is the README for your extension "imagemarkpengent". After writing up a brief description, we recommend including the following sections.
|
This is the README for your extension "imagemarkpengent". After writing up a brief description, we recommend including the following sections.
|
||||||
|
|
||||||
|
@ -32,14 +32,16 @@ VSCode拡張機能「ImageMarkPengent」の実装タスク一覧です。
|
|||||||
- [x] 対象拡張子を `.png` / `.jpg` / `.jpeg` に限定
|
- [x] 対象拡張子を `.png` / `.jpg` / `.jpeg` に限定
|
||||||
|
|
||||||
### 2. WebViewで画像表示
|
### 2. WebViewで画像表示
|
||||||
- [ ] コマンド実行時にWebViewパネルを開く
|
- [x] コマンド実行時にWebViewパネルを開く
|
||||||
- [ ] 画像ファイルを `<img>` または `<canvas>` で表示
|
- [x] 画像ファイルを `<img>` または `<canvas>` で表示
|
||||||
- [ ] 画像の上に描き込み可能な `canvas` を重ねる
|
- [x] 画像の上に描き込み可能な `canvas` を重ねる
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 3. 赤丸マーク描画機能
|
### 3. 赤丸マーク描画機能
|
||||||
- [ ] WebView内でJavaScriptにより `<canvas>` へ赤丸を描画
|
- [x] WebView内でJavaScriptにより `<canvas>` へ赤丸を描画
|
||||||
- [ ] クリック座標を取得し、赤丸を描画・保持
|
- [x] クリック座標を取得し、赤丸を描画・保持
|
||||||
- [ ] 複数マークの描画・再描画に対応
|
- [x] 複数マークの描画・再描画に対応
|
||||||
|
|
||||||
### 4. 加工画像の保存機能
|
### 4. 加工画像の保存機能
|
||||||
- [ ] WebViewに「保存」ボタンを設置
|
- [ ] WebViewに「保存」ボタンを設置
|
||||||
@ -47,10 +49,3 @@ VSCode拡張機能「ImageMarkPengent」の実装タスク一覧です。
|
|||||||
- [ ] `vscode.postMessage()` で拡張機能側に送信
|
- [ ] `vscode.postMessage()` で拡張機能側に送信
|
||||||
- [ ] 拡張機能側でファイル保存(別名保存も対応)
|
- [ ] 拡張機能側でファイル保存(別名保存も対応)
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 今後の拡張案(任意)
|
|
||||||
- 番号付きマーカーや矢印の追加
|
|
||||||
- マークの色やサイズ変更
|
|
||||||
- Undo/Redo機能
|
|
||||||
- 他画像フォーマット対応
|
|
@ -37,6 +37,29 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
|
|
||||||
const imageSrc = panel.webview.asWebviewUri(uri);
|
const imageSrc = panel.webview.asWebviewUri(uri);
|
||||||
panel.webview.html = getWebviewContent(imageSrc.toString());
|
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);
|
context.subscriptions.push(disposable);
|
||||||
|
@ -30,6 +30,7 @@ export function getWebviewContent(imageSrc: string): string {
|
|||||||
<button id="moveBtn" class="active-btn">移動モード</button>
|
<button id="moveBtn" class="active-btn">移動モード</button>
|
||||||
<button id="selectBtn">選択モード</button>
|
<button id="selectBtn">選択モード</button>
|
||||||
<button id="markBtn">マーク追加(Ctr+左クリック)</button>
|
<button id="markBtn">マーク追加(Ctr+左クリック)</button>
|
||||||
|
<button id="saveBtn">保存</button>
|
||||||
<label>太さ:
|
<label>太さ:
|
||||||
<select id="lineWidth">
|
<select id="lineWidth">
|
||||||
<option value="2">2px</option>
|
<option value="2">2px</option>
|
||||||
@ -54,6 +55,7 @@ export function getWebviewContent(imageSrc: string): string {
|
|||||||
const markBtn = document.getElementById('markBtn');
|
const markBtn = document.getElementById('markBtn');
|
||||||
const lineWidthSelect = document.getElementById('lineWidth');
|
const lineWidthSelect = document.getElementById('lineWidth');
|
||||||
const colorPicker = document.getElementById('colorPicker');
|
const colorPicker = document.getElementById('colorPicker');
|
||||||
|
const saveBtn = document.getElementById('saveBtn');
|
||||||
|
|
||||||
// 状態
|
// 状態
|
||||||
let mode = 'move'; // 'move' or 'mark' or 'select'
|
let mode = 'move'; // 'move' or 'mark' or 'select'
|
||||||
@ -115,6 +117,24 @@ export function getWebviewContent(imageSrc: string): string {
|
|||||||
if (changed) pushUndo();
|
if (changed) pushUndo();
|
||||||
draw();
|
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) {
|
function setMode(newMode) {
|
||||||
mode = 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;
|
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();
|
resizeCanvas();
|
||||||
// 最初の状態をundoStackにpush
|
// 最初の状態をundoStackにpush
|
||||||
|
Loading…
x
Reference in New Issue
Block a user