이번에는 Image Scaling (확대/축소) 및 Image Panning(이동) UI 구현에 대해 WPF OpenCV 프로젝트에 추가 하도록 하겠습니다.
지난 포스팅까지 기본 UI를 만들어서 이미지를 띄워봤는데요. 혹시 큰 해상도의 이미지를 불러와 보셨나요? 아마 이미지가 ImageView 영역보다 크면 짤려서 나오고, 마우스로 움직이려 해도 꼼짝도 하지 않는 ‘애물단지’ 같은 상태였을 겁니다. 이러한 문제를 해결하기 위해, WPF OpenCV 이미지 확대와 같은 방법을 고려해볼 수 있습니다.
그래서 이번 시간에는 UI에 살을 좀 붙여보려 합니다. 정확히는 “이미지 확대/축소(Zoom)“와 “이미지 이동(Panning)” 기능을 위한 UI 기초 공사입니다. 이 기능을 넣어야 큰 이미지도 화면에 딱 맞춰서(Fit) 볼 수 있고, 세밀한 검사를 위해 확대해서 볼 수도 있으니까요.
WPF OpenCV 이미지 확대 기능을 통해 사용자는 이미지의 세부 사항을 더 잘 볼 수 있게 됩니다. 이를 통해 프로젝트의 완성도를 높일 수 있습니다.
UI 구조 변경: 액자(Border)와 도화지(Canvas)
기존에는 단순히 <Image> 컨트롤 하나만 덩그러니 있었습니다. 이것을 Zoom과 Panning이 가능한 구조로 바꾸려면, 아래와 같이 감싸주는 부모 컨트롤들이 필요합니다.
기존 MainWindow.xaml에서 <Image ... /> 부분을 주석 처리하고, 아래 코드로 교체해 주세요.
<Border Grid.Row="1" x:Name="ZoomBorder"
BorderBrush="#333" BorderThickness="1" Background="#333333"
ClipToBounds="True"
MouseWheel="ZoomBorder_MouseWheel"
MouseDown="ZoomBorder_MouseDown"
MouseUp="ZoomBorder_MouseUp"
MouseMove="ZoomBorder_MouseMove"
SizeChanged="ZoomBorder_SizeChanged"
Loaded="ZoomBorder_Loaded">
<Canvas x:Name="ImgCanvas">
<Image x:Name="ImgView" Source="{Binding DisplayImage}" Stretch="None"
RenderOptions.BitmapScalingMode="NearestNeighbor"
HorizontalAlignment="Left"
VerticalAlignment="Top"
SizeChanged="ImgView_SizeChanged">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="imgScale"/>
<TranslateTransform x:Name="imgTranslate"/>
</TransformGroup>
</Image.RenderTransform>
</Image>
</Canvas>
</Border>
구조가 꽤 복잡해졌죠? 왜 이렇게 감싸야 하는지 하나씩 뜯어보겠습니다.
핵심 속성
① Border와 ClipToBounds=”True”
가장 바깥을 감싸는 Border는 ‘액자 틀’이라고 생각하시면 됩니다. 여기서 가장 중요한 속성은 ClipToBounds="True“입니다.
이 속성은 “액자 밖으로 튀어 나간 그림을 가위로 자를까요?”라고 묻는 것입니다.
- False: 이미지를 확대해서 이동시키면, 이미지가 옆에 있는 버튼이나 목록창을 침범해서 유령처럼 둥둥 떠다니게 됩니다.
- True: 액자(Border) 영역을 벗어나는 순간 가차 없이 잘라버립니다(숨깁니다).
확대 기능을 넣을 때 이 속성이 없으면 UI가 엉망이 되므로 필수입니다.

② Canvas의 역할
Border 안에 바로 Image를 넣지 않고, 굳이 Canvas를 중간에 둔 이유가 뭘까요? Image 컨트롤을 Border에 직접 넣으면, WPF는 “어? 이미지가 크네? 그럼 Border도 같이 커져야지!” 하고 자동으로 크기를 1:1로 맞춰버립니다.
우리는 이미지가 아무리 커도 화면(Window) 크기에 맞춰서 축소해서 보여줘야 합니다. Canvas는 자식(Image)의 크기를 부모(Border)에게 알리지 않습니다. 덕분에 우리는 코드로 “이미지 크기는 신경 쓰지 말고, 현재 윈도우 크기에 맞춰서 비율을 계산해!”라고 FitImageToScreen() 같은 함수를 만들 수 있게 됩니다.
③ RenderTransform (렌즈 조작하기)
영상 처리 프로젝트에서 가장 중요한 원칙은 “원본 데이터(픽셀)를 건드리지 말라”는 것입니다. 픽셀을 임의로 제거하거나 뭉개버리면 좌표 계산이나 거리 측정에 오류가 생기니까요.
RenderTransform은 원본 이미지는 그대로 두고, 모니터에 보여주는 ‘렌즈’만 조작하는 기능입니다.
TransformGroup: 확대(Scale)도 해야 하고 이동(Translate)도 해야 하니, 이 두 가지 변형 도구를 한 바구니에 담는 역할을 합니다.
ScaleTransform: 가로(ScaleX), 세로(ScaleY) 배율을 조절합니다.
TranslateTransform: 가로(X), 세로(Y) 위치를 이동 시킵니다. (예: X=100이면 오른쪽으로 100픽셀 이동)
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="imgScale"/>
<TranslateTransform x:Name="imgTranslate"/>
</TransformGroup>
</Image.RenderTransform>
🚀 다음 단계
자, 이것으로 화면을 마음대로 주무를 수 있는 UI 설계(XAML)는 끝났습니다. 하지만 지금 당장 실행해보면 아무런 변화가 없을 겁니다. 왜냐하면 마우스 휠을 굴렸을 때 “확대해라!”라고 명령을 내리는 C# 코드(이벤트 핸들러)가 아직 없기 때문이죠.
다음 포스팅에서는 오늘 선언한 ZoomBorder_MouseWheel 같은 이벤트 함수들을 MainWindow.xaml.cs에 직접 구현해서, 실제로 이미지가 부드럽게 확대되고 이동하는 모습을 만들어 보겠습니다.
이번 포스팅의 각 키워드 마다 관련 링크를 걸어 두었습니다. 자세한 내용은 한번쯤은 참고해 보는 것도 많은 도움이 될 거라 생각합니다. 아래에 참고자료를 따로 링크해 두었습니다. 참고해 주세요.
참고자료
Border(액자 컨트롤): 테두리를 그리고 배경을 설정하는 Border 클래스
https://learn.microsoft.com/ko-kr/dotnet/api/system.windows.uielement.cliptobounds
ClipToBounds(영역 잘라내기): 부모 컨트롤 영역을 벗어나는 자식 요소를 잘라낼지 결정하는 속성
https://learn.microsoft.com/ko-kr/dotnet/api/system.windows.uielement.cliptobounds
Canvas(절대 좌표 패널): 자식 요소를 절대 좌표(x, y)로 배치할 수 있는 레이아웃 패널
https://learn.microsoft.com/ko-kr/dotnet/api/system.windows.controls.canvas
WPF 변환(Transforms) 개요: WPF에서 객체를 회전, 크기 조절, 왜곡, 이동시키는 변환 개념을 정리한 문서
https://learn.microsoft.com/ko-kr/dotnet/desktop/wpf/graphics-multimedia/transforms-overview
RenderTransform (렌더링 변환): UI 요소가 화면에 그려지기 직전에 변형을 가하는 속성
https://learn.microsoft.com/ko-kr/dotnet/api/system.windows.uielement.rendertransform
ScaleTransform (확대/축소): 2 차원 x-y 좌표계에서 객체의 크기를 조절하는 클래스
https://learn.microsoft.com/ko-kr/dotnet/api/system.windows.media.scaletransform
TranslateTransform (이동/Panning): 2 차원 x-y 좌표계에서 객체를 이동(변위)시키는 클래스
https://learn.microsoft.com/ko-kr/dotnet/api/system.windows.media.translatetransform