Restructure SAM rough mask workflow for sidebar preview

- Add roughMaskThumbnailScale state with $watch to sync with main scale slider
- Update sidebar thumbnail to use transform:scale() for consistent zoom between views
- Modify openRoughMaskInNewWindow() to create HTML page with matching scale
- Add denoise strength slider (10-100%) visible only when rough mask exists
- Backend already supports denoise_strength parameter in prepare_mask_workflow_with_start()
- Rough mask auto-clears after successful extraction
- Add Playwright tests for UI changes and API parameter acceptance
This commit is contained in:
2026-03-28 10:42:27 -07:00
parent c94988561c
commit 7c9a25dd91
6 changed files with 240 additions and 72 deletions

View File

@@ -132,14 +132,15 @@ function oraEditor() {
polygonWidth: 2,
polygonPreviewUrl: null,
// SAM rough mask state
isSamMode: false,
samIncludePoints: [],
samExcludePoints: [],
isSamGenerating: false,
samMaskPath: null,
samMaskUrl: null,
useSamAsStart: true,
// SAM rough mask state
isSamMode: false,
samIncludePoints: [],
samExcludePoints: [],
isSamGenerating: false,
roughMaskPath: null,
roughMaskUrl: null,
denoiseStrength: 80,
roughMaskThumbnailScale: 25,
// Mask extraction
maskSubject: '',
@@ -179,6 +180,10 @@ function oraEditor() {
console.log('ORA Editor initialized');
this.setupKeyHandlers();
},
$watch('scale', (newScale) => {
this.roughMaskThumbnailScale = newScale;
}),
setupKeyHandlers() {
document.addEventListener('keydown', (e) => {
@@ -323,8 +328,9 @@ function oraEditor() {
this.isSamMode = false;
this.samIncludePoints = [];
this.samExcludePoints = [];
this.samMaskPath = null;
this.samMaskUrl = null;
this.roughMaskPath = null;
this.roughMaskUrl = null;
this.roughMaskThumbnailScale = 25;
const canvas = document.getElementById('polygonCanvas');
if (canvas) {
canvas.style.display = 'none';
@@ -608,15 +614,15 @@ function oraEditor() {
this.isSamMode = true;
this.samIncludePoints = [];
this.samExcludePoints = [];
this.samMaskPath = null;
this.samMaskUrl = null;
this.roughMaskPath = null;
this.roughMaskUrl = null;
},
clearSamPoints() {
this.samIncludePoints = [];
this.samExcludePoints = [];
this.samMaskPath = null;
this.samMaskUrl = null;
this.roughMaskPath = null;
this.roughMaskUrl = null;
},
handleSamClick(e) {
@@ -684,10 +690,11 @@ function oraEditor() {
console.log('[ORA EDITOR] SAM response:', data);
if (!data.success) throw new Error(data.error || 'Failed');
this.samMaskPath = data.mask_path;
this.samMaskUrl = data.mask_url;
if (!data.success) throw new Error(data.error || 'Failed');
this.roughMaskPath = data.mask_path;
this.roughMaskUrl = data.mask_url;
this.roughMaskThumbnailScale = this.scale;
this.isSamMode = false;
} catch (e) {
console.error('[ORA EDITOR] Error generating SAM mask:', e);
@@ -697,22 +704,59 @@ function oraEditor() {
}
},
useSamMask() {
if (this.samMaskPath) {
this.tempMaskPath = this.samMaskPath;
this.tempMaskUrl = this.samMaskUrl;
this.tempMaskPaths = [this.samMaskPath];
this.currentMaskIndex = 0;
this.showMaskModal = true;
}
},
discardSamMask() {
this.samMaskPath = null;
this.samMaskUrl = null;
discardRoughMask() {
this.roughMaskPath = null;
this.roughMaskUrl = null;
this.samIncludePoints = [];
this.samExcludePoints = [];
},
openRoughMaskInNewWindow() {
if (!this.roughMaskUrl) return;
const win = window.open('', '_blank');
const scale = this.scale / 100;
const scaledWidth = this.imageWidth * scale;
const scaledHeight = this.imageHeight * scale;
win.document.write(`<!DOCTYPE html>
<html>
<head>
<title>Rough Mask Preview</title>
<style>
body {
margin: 0;
padding: 20px;
background: #1f2937;
color: white;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
.info { margin-bottom: 10px; text-align: center; }
.container {
transform-origin: top left;
}
img {
display: block;
opacity: 0.7;
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
}
</style>
</head>
<body>
<div class="info">
<h2>Rough Mask Preview</h2>
<p>Original: ${this.imageWidth}x${this.imageHeight}px | Scale: ${(scale * 100).toFixed(0)}%</p>
</div>
<div class="container" style="transform: scale(${scale});">
<img src="${this.roughMaskUrl}" alt="Rough Mask" style="width: ${this.imageWidth}px; height: ${this.imageHeight}px;">
</div>
</body>
</html>`);
win.document.close();
},
// === Mask Extraction ===
async extractMask() {
@@ -727,12 +771,13 @@ function oraEditor() {
use_polygon: this.usePolygonHint && this.polygonPoints.length >= 3,
ora_path: this.oraPath,
comfy_url: this.comfyUrl,
count: this.maskCount
count: this.maskCount,
denoise_strength: this.denoiseStrength / 100
};
if (this.useSamAsStart && this.samMaskPath) {
requestBody.start_mask_path = this.samMaskPath;
console.log('[ORA EDITOR] Using SAM mask as starting point:', this.samMaskPath);
if (this.roughMaskPath) {
requestBody.start_mask_path = this.roughMaskPath;
console.log('[ORA EDITOR] Using rough mask as starting point:', this.roughMaskPath);
}
try {
@@ -752,6 +797,12 @@ function oraEditor() {
this.currentMaskIndex = 0;
this.tempMaskPath = this.tempMaskPaths[0];
this.tempMaskUrl = data.mask_urls?.[0] || '/api/file/mask?path=' + encodeURIComponent(this.tempMaskPath);
// Clear rough mask after successful extraction
this.roughMaskPath = null;
this.roughMaskUrl = null;
this.isSamMode = false;
this.showMaskModal = true;
} catch (e) {
console.error('[ORA EDITOR] Error extracting mask:', e);
@@ -825,6 +876,9 @@ function oraEditor() {
this.tempMaskUrl = null;
this.tempMaskPaths = [];
this.currentMaskIndex = 0;
// Clear SAM mode when mask preview dismissed (user decided on something else or cancelled)
this.isSamMode = false;
},
// === Krita Integration ===