Files
2026-09-16 17:22:14 +09:00

66 lines
4.8 KiB
Markdown

# Research: 화면 캡쳐 → 챗 첨부 (Phase 0)
브레인스토밍서 큰 결정(챗에 얹기·자체 오버레이·v1 캡쳐만·핫키 Ctrl+Shift+9·순수 첨부까지)은 끝. 여기선 **구현 미해결점**만.
---
## R1. 화면 캡쳐 API
**결정**: **`System.Drawing.Common`** 패키지 + `Graphics.CopyFromScreen(srcX, srcY, 0, 0, size)``Bitmap` → PNG(`MemoryStream`) → base64.
**Rationale**: 스크린샷 고전 방식, 코드 최소. WPF 프로젝트라 기본 참조엔 없어 패키지 추가(Windows 전용 API지만 이 앱은 Windows 데스크톱). `CodeAssist.Shell.csproj`에 추가.
**Alternatives 기각**: GDI P/Invoke(BitBlt+GetDC/BitmapSource) — 패키지 없이 되지만 코드 많고 손 많이 감. Windows.Graphics.Capture(WinRT) — 최신이나 interop 복잡, YAGNI.
## R2. 좌표·DPI 변환
**결정**: 오버레이는 WPF DIP 좌표로 그리고, **선택 사각형(DIP) → 물리 픽셀**로 변환해 `CopyFromScreen`에 넘긴다.
- 오버레이 창은 가상화면(`SystemParameters.VirtualScreen*`, DIP) 전체를 덮음.
- 변환: 오버레이의 DPI 스케일(`VisualTreeHelper.GetDpi(this)` 또는 `PresentationSource``CompositionTarget.TransformToDevice`)로 (오버레이 원점 + 선택 DIP) → 물리 px.
- 앱을 **PerMonitorV2 DPI 인지**로(app.manifest) 두어 스케일 창서도 좌표가 맞게.
**ponytail 천장**: 서로 배율 다른 멀티모니터(mixed-DPI)에선 가상화면 한 DPI로 계산하면 어긋날 수 있음 → v1은 단일/동일배율 케이스 맞추고, mixed-DPI 보정은 추후. `ponytail:` 주석으로 박음.
## R3. 오버레이 창
**결정**: 신규 `CaptureOverlayWindow`(WPF Window).
- `WindowStyle.None` + `AllowsTransparency=true` + 반투명 어두운 배경(예: `#55000000`) + `Topmost=true` + `ShowInTaskbar=false`.
- 위치·크기 = `VirtualScreen`(DIP) 전체(멀티모니터 커버). `WindowStartupLocation=Manual`.
- 위에 `Canvas` + 선택 `Rectangle`. MouseDown(시작점)→MouseMove(사각형 갱신)→MouseUp(확정). `Cursor=Cross`.
- **취소**: `Esc` KeyDown, 또는 선택 크기 임계(예: <4px) 미만이면 캡쳐 없이 닫음. 포커스 잃어도(Deactivated) 닫아 안전.
- 캡쳐 완료 시 `Captured(string dataUrl)` 이벤트, 취소 시 `Cancelled` — App이 구독. 처리 후 창 Close.
**Rationale**: 스니핑툴류 표준. 팔레트 창과 별개의 짧은 수명 창.
## R4. 이미지 전달(C#→JS)
**결정**: 기존 `SendToWeb`(`PostWebMessageAsJson`) 채널로 `{type:"capture.image", dataUrl}`(PNG base64 data URL). `PaletteWindow.SendCaptureImage(dataUrl)` 패스스루 추가.
**ponytail 천장**: 대영역 캡쳐면 base64가 수 MB → PostWebMessageAsJson 부담. v1은 그대로, 추후 임시파일(virtual host mapping) 또는 blob 전달로 최적화.
## R5. 프론트 수신(마운트 레이스)
**결정**: `bridgeNavigate` 허브(C#→JS 메시지 단일 리스너)에 `capture.image` 분기 추가.
- 모듈에 `pendingCaptureImage` 보관 + `bridge:captureImage` CustomEvent 발생.
- **read-once(consume) 세만틱**: `consumePendingCaptureImage()`가 값 반환 후 비움. Composer가 **마운트 시 consume**(네비 직후 이벤트를 놓쳐도 반영) + **이벤트 구독**(이미 마운트된 경우). 둘 다 consume/clear 하므로 같은 이미지 이중 첨부 안 됨.
**Rationale**: pasteTarget/bridgeNavigate에서 검증된 "모듈 보관 + 이벤트" 레이스 패턴 재사용. 단 캡쳐는 표시 지속이 아니라 1회 소비라 consume 세만틱.
## R6. 챗 첨부·도착 흐름
**결정**:
- App 캡쳐 완료 → `ShowPalette()`(앞창 캡처+표시) + `NavigateApp("/snap/new")` + `SendCaptureImage(dataUrl)`.
- Composer에 **이미지 첨부 상태**(`imageAttachments: string[]` data URL) 신규 — 텍스트 첨부 칩과 별개. 썸네일 `<img>` + X(제거). 마운트 시 pending 캡쳐 이미지 consume해 첨부, 이후 이벤트로 추가(누적).
- **v1 전송 동작**: 메시지 전송 시 이미지 첨부는 **전송하지 않음**(FR-009). 텍스트만 기존대로. 전송 후 이미지 첨부 처리는 data-model에서 확정(§전송 시).
**Rationale**: 도착지 새 대화(`/snap/new`)는 spec Assumption. Composer는 이미 텍스트 첨부 칩 패턴이 있어 이미지 첨부도 같은 자리에.
## R7. 캡쳐 소유·조립
**결정**: 드래그·DPI변환·`CopyFromScreen``CaptureOverlayWindow`(선택 rect·DPI를 아는 곳)에서. 순수 픽셀 캡쳐(물리 x,y,w,h → PNG 바이트)는 `Shell/Platform/ScreenCapture.cs`로 분리(오버레이는 UI, 캡쳐는 Win32 — 관심사 분리, 좌표변환 순수부는 단위테스트 여지).
## 구현 시점 확인
- `System.Drawing.Common` 버전 핀(최신 안정).
- app.manifest에 PerMonitorV2 dpiAwareness 설정 여부 확인(이미 있으면 재사용).