4.8 KiB
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. - 취소:
EscKeyDown, 또는 선택 크기 임계(예: <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:captureImageCustomEvent 발생. - 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 설정 여부 확인(이미 있으면 재사용).