diff --git a/src/rmbg_as_a_service/server.py b/src/rmbg_as_a_service/server.py index 15945d6..c4d6fa0 100644 --- a/src/rmbg_as_a_service/server.py +++ b/src/rmbg_as_a_service/server.py @@ -41,13 +41,15 @@ from pathlib import Path import litserve as ls from fastapi import HTTPException -from fastapi.responses import HTMLResponse +from fastapi.responses import HTMLResponse, Response from PIL import Image, ImageOps from .model import BiRefNetService from .prompt_segment import PromptSegmenter -_UI_HTML = (Path(__file__).parent / "static" / "index.html").read_text(encoding="utf-8") +_STATIC = Path(__file__).parent / "static" +_UI_HTML = (_STATIC / "index.html").read_text(encoding="utf-8") +_UI_CSS = (_STATIC / "styles.css").read_text(encoding="utf-8") # Lazily-created prompt segmenter (DINO + SAM), shared by the /segment route. _segmenter: PromptSegmenter | None = None @@ -132,6 +134,10 @@ def run() -> None: def index() -> str: return _UI_HTML + @server.app.get("/styles.css") + def styles() -> Response: + return Response(_UI_CSS, media_type="text/css") + @server.app.post("/segment") def segment(payload: dict) -> dict: """Prompt-conditioned segmentation (GroundingDINO + SAM).""" diff --git a/src/rmbg_as_a_service/static/index.html b/src/rmbg_as_a_service/static/index.html index eec01c9..7819c9c 100644 --- a/src/rmbg_as_a_service/static/index.html +++ b/src/rmbg_as_a_service/static/index.html @@ -3,312 +3,350 @@ -Background Removal & Segmentation - +RMBG_SERVICE // BACKGROUND REMOVAL + -
-

Background Removal & Segmentation

-
Automatic removal, or prompt-conditioned segmentation.
+
-
- - -
+ -
-

Drop an image here or click to choose

-

No file selected

- -
+ + + + +
+
+ + +
+
+
[ NO IMAGE LOADED ]
+ + +
+
+
+ diff --git a/src/rmbg_as_a_service/static/styles.css b/src/rmbg_as_a_service/static/styles.css index 2b7b512..6c65f70 100644 --- a/src/rmbg_as_a_service/static/styles.css +++ b/src/rmbg_as_a_service/static/styles.css @@ -652,5 +652,368 @@ label { /* Rotate caret when drawer is open */ .drawer.open .drawer-caret { - transform: rotate(120deg); + transform: rotate(90deg); +} + +/* ============================================================ + APP-SPECIFIC — RMBG_SERVICE background-removal UI. + Extends the brutalist terminal system above; same palette: + #000 ground, #8fbc8f primary, #7aa87a dim, #ff6b6b alert. + ============================================================ */ + +/* --- sidebar header --- */ +.app-header { + border-bottom: 2px solid #8fbc8f; + padding-bottom: 12px; +} + +.app-title { + font-size: 22px; + font-weight: bold; + letter-spacing: 3px; + color: #8fbc8f; +} + +.app-sub { + font-size: 10px; + letter-spacing: 1px; + color: #7aa87a; + margin-top: 6px; +} + +/* --- selects (matches brutalist text/number inputs) --- */ +.select-wrap { + position: relative; + width: 100%; +} + +.select-wrap::after { + content: "\25BC"; + position: absolute; + right: 0; + top: 1px; + bottom: 1px; + width: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 8px; + color: #000000; + background: #8fbc8f; + pointer-events: none; +} + +select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background: #000000; + border: 1px solid #8fbc8f; + color: #8fbc8f; + padding: 6px 28px 6px 8px; + font-family: inherit; + font-size: 12px; + font-weight: bold; + text-transform: uppercase; + width: 100%; + cursor: pointer; + border-radius: 0; +} + +select:hover { + background: #0a0a0a; +} + +select:focus { + outline: 2px solid #8fbc8f; +} + +select option { + background: #000000; + color: #8fbc8f; +} + +/* --- radio / checkbox rows --- */ +.radio-row { + display: flex; + flex-direction: column; + gap: 8px; +} + +.radio-row label, +.check-row { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + font-weight: bold; +} + +/* --- slider label units / state --- */ +.slider-container .control-label { + display: flex; + align-items: baseline; + gap: 6px; +} + +.unit { + color: #7aa87a; + font-size: 11px; + font-weight: normal; +} + +.slider-container.disabled, +.control-group.disabled { + opacity: 0.3; + pointer-events: none; +} + +/* breathing room between stacked controls inside the OUTPUT drawer */ +.drawer-content { + display: flex; + flex-direction: column; + gap: 14px; +} + +/* --- help tooltip (carried over, restyled brutalist) --- */ +.help { + display: inline-flex; + align-items: center; + justify-content: center; + width: 14px; + height: 14px; + margin-left: auto; + border: 1px solid #8fbc8f; + color: #8fbc8f; + font-size: 9px; + font-weight: bold; + cursor: help; + position: relative; + flex-shrink: 0; +} + +.help:hover { + background: #8fbc8f; + color: #000000; +} + +.help:hover::after { + content: attr(data-tip); + position: absolute; + bottom: 150%; + right: 0; + width: 220px; + background: #000000; + color: #8fbc8f; + border: 1px solid #8fbc8f; + padding: 8px 10px; + font-size: 11px; + font-weight: normal; + line-height: 1.5; + text-transform: none; + z-index: 200; + pointer-events: none; +} + +/* --- action buttons --- */ +.action-row { + display: flex; + flex-direction: column; + gap: 8px; +} + +.action-btn { + width: 100%; + text-align: center; +} + +.action-btn.primary { + background: #8fbc8f; + color: #000000; +} + +.action-btn.primary:hover { + background: #7aa87a; +} + +.action-btn:disabled { + border-color: #444; + color: #555; + background: #000000; + cursor: not-allowed; +} + +#dl { + display: block; + text-decoration: none; +} + +.reset-btn { + width: 100%; + text-align: center; +} + +/* --- status / hint --- */ +.status { + font-size: 12px; + color: #7aa87a; + min-height: 16px; + line-height: 1.5; + word-break: break-word; +} + +.status.err { + color: #ff6b6b; +} + +.hint { + font-size: 11px; + color: #5a7a5a; + line-height: 1.6; + text-transform: none; + border-left: 2px solid #2e3e2e; + padding-left: 8px; +} + +/* --- preview area --- */ +.preview-tabs { + position: absolute; + top: 12px; + left: 12px; + display: flex; + z-index: 10; +} + +.preview-tab { + background: #000000; + border: 1px solid #8fbc8f; + color: #7aa87a; + padding: 6px 16px; + font-size: 11px; + cursor: pointer; +} + +.preview-tab+.preview-tab { + border-left: none; +} + +.preview-tab.active { + background: #8fbc8f; + color: #000000; +} + +.preview-tab:disabled { + border-color: #444; + color: #444; + cursor: not-allowed; +} + +.preview-stage { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; +} + +.preview-canvas { + cursor: zoom-in; +} + +.no-preview { + letter-spacing: 2px; +} + +/* checkerboard behind transparent result pixels — green-on-black */ +.preview-canvas.checker, +.lb-stage img.checker { + background-image: + linear-gradient(45deg, #1a2a1a 25%, transparent 25%), + linear-gradient(-45deg, #1a2a1a 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #1a2a1a 75%), + linear-gradient(-45deg, transparent 75%, #1a2a1a 75%); + background-size: 22px 22px; + background-position: 0 0, 0 11px, 11px -11px, -11px 0; + background-color: #000000; +} + +/* upload box thumbnail state */ +.upload-box.has-file { + border-color: #7aa87a; +} + +.upload-box.has-file .upload-text { + color: #7aa87a; +} + +/* --- lightbox --- */ +.lightbox { + position: fixed; + inset: 0; + z-index: 1000; + background: rgba(0, 0, 0, 0.97); + display: flex; + align-items: center; + justify-content: center; +} + +.lightbox[hidden] { + display: none; +} + +.lb-stage { + width: 100vw; + height: 100vh; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; +} + +.lb-stage img { + max-width: 100vw; + max-height: 100vh; + transform-origin: 0 0; + cursor: grab; + user-select: none; + -webkit-user-drag: none; + will-change: transform; +} + +.lb-stage.grabbing img { + cursor: grabbing; +} + +.lb-bar { + position: fixed; + top: 0; + left: 0; + right: 0; + padding: 14px 20px; + z-index: 2; + display: flex; + justify-content: space-between; + align-items: center; + color: #7aa87a; + font-size: 11px; + letter-spacing: 1px; + pointer-events: none; +} + +.lb-close { + pointer-events: auto; + background: #8fbc8f; + color: #000000; + border: 2px solid #8fbc8f; + width: 32px; + height: 32px; + font-size: 14px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.lb-close:hover { + background: #7aa87a; }