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

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) 또는 PresentationSourceCompositionTarget.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변환·CopyFromScreenCaptureOverlayWindow(선택 rect·DPI를 아는 곳)에서. 순수 픽셀 캡쳐(물리 x,y,w,h → PNG 바이트)는 Shell/Platform/ScreenCapture.cs로 분리(오버레이는 UI, 캡쳐는 Win32 — 관심사 분리, 좌표변환 순수부는 단위테스트 여지).

구현 시점 확인

  • System.Drawing.Common 버전 핀(최신 안정).
  • app.manifest에 PerMonitorV2 dpiAwareness 설정 여부 확인(이미 있으면 재사용).