Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pan & Zoom Image

I want to create a simple image viewer in WPF that will enable the user to:

  • Pan (by mouse dragging the image).
  • Zoom (with a slider).
  • Show overlays (rectangle selection for example).
  • Show original image (with scroll bars if needed).

Can you explain how to do it?

I didn't find a good sample on the web. Should I use ViewBox? Or ImageBrush? Do I need ScrollViewer?

like image 245
Yuval Peled Avatar asked Apr 12 '09 15:04

Yuval Peled


People also ask

Siapa itu pan?

Pan (パン) adalah protagonis dalam manga Dragon Ball dan anime Dragon Ball Z dan Dragon Ball GT. Dia adalah cucu dari protagonis utama dari serial Dragon Ball, Songoku. Warisan Pan terutama Manusia, menjadi keturunan dari hibrida manusia Saiya Songohan dan Videl, sehingga membuat Saiya 1/4-nya.

Siapa pemimpin partai PAN pada masa Orde Baru?

Ketua Umum saat ini adalah Zulkifli Hasan. Ketua Majelis Pertimbangan Partai dijabat oleh Hatta Rajasa, sedangkan Ketua Dewan Kehormatan Partai dijabat oleh Soetrisno Bachir.


2 Answers

After using samples from this question I've made complete version of pan & zoom app with proper zooming relative to mouse pointer. All pan & zoom code has been moved to separate class called ZoomBorder.

ZoomBorder.cs

using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media;  namespace PanAndZoom {   public class ZoomBorder : Border   {     private UIElement child = null;     private Point origin;     private Point start;      private TranslateTransform GetTranslateTransform(UIElement element)     {       return (TranslateTransform)((TransformGroup)element.RenderTransform)         .Children.First(tr => tr is TranslateTransform);     }      private ScaleTransform GetScaleTransform(UIElement element)     {       return (ScaleTransform)((TransformGroup)element.RenderTransform)         .Children.First(tr => tr is ScaleTransform);     }      public override UIElement Child     {       get { return base.Child; }       set       {         if (value != null && value != this.Child)           this.Initialize(value);         base.Child = value;       }     }      public void Initialize(UIElement element)     {       this.child = element;       if (child != null)       {         TransformGroup group = new TransformGroup();         ScaleTransform st = new ScaleTransform();         group.Children.Add(st);         TranslateTransform tt = new TranslateTransform();         group.Children.Add(tt);         child.RenderTransform = group;         child.RenderTransformOrigin = new Point(0.0, 0.0);         this.MouseWheel += child_MouseWheel;         this.MouseLeftButtonDown += child_MouseLeftButtonDown;         this.MouseLeftButtonUp += child_MouseLeftButtonUp;         this.MouseMove += child_MouseMove;         this.PreviewMouseRightButtonDown += new MouseButtonEventHandler(           child_PreviewMouseRightButtonDown);       }     }      public void Reset()     {       if (child != null)       {         // reset zoom         var st = GetScaleTransform(child);         st.ScaleX = 1.0;         st.ScaleY = 1.0;          // reset pan         var tt = GetTranslateTransform(child);         tt.X = 0.0;         tt.Y = 0.0;       }     }      #region Child Events          private void child_MouseWheel(object sender, MouseWheelEventArgs e)         {             if (child != null)             {                 var st = GetScaleTransform(child);                 var tt = GetTranslateTransform(child);                  double zoom = e.Delta > 0 ? .2 : -.2;                 if (!(e.Delta > 0) && (st.ScaleX < .4 || st.ScaleY < .4))                     return;                  Point relative = e.GetPosition(child);                 double absoluteX;                 double absoluteY;                  absoluteX = relative.X * st.ScaleX + tt.X;                 absoluteY = relative.Y * st.ScaleY + tt.Y;                  st.ScaleX += zoom;                 st.ScaleY += zoom;                  tt.X = absoluteX - relative.X * st.ScaleX;                 tt.Y = absoluteY - relative.Y * st.ScaleY;             }         }          private void child_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)         {             if (child != null)             {                 var tt = GetTranslateTransform(child);                 start = e.GetPosition(this);                 origin = new Point(tt.X, tt.Y);                 this.Cursor = Cursors.Hand;                 child.CaptureMouse();             }         }          private void child_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)         {             if (child != null)             {                 child.ReleaseMouseCapture();                 this.Cursor = Cursors.Arrow;             }         }          void child_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)         {             this.Reset();         }          private void child_MouseMove(object sender, MouseEventArgs e)         {             if (child != null)             {                 if (child.IsMouseCaptured)                 {                     var tt = GetTranslateTransform(child);                     Vector v = start - e.GetPosition(this);                     tt.X = origin.X - v.X;                     tt.Y = origin.Y - v.Y;                 }             }         }          #endregion     } } 

MainWindow.xaml

<Window x:Class="PanAndZoom.MainWindow"         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"         xmlns:local="clr-namespace:PanAndZoom"         Title="PanAndZoom" Height="600" Width="900" WindowStartupLocation="CenterScreen">     <Grid>         <local:ZoomBorder x:Name="border" ClipToBounds="True" Background="Gray">             <Image Source="image.jpg"/>         </local:ZoomBorder>     </Grid> </Window> 

MainWindow.xaml.cs

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes;  namespace PanAndZoom {     public partial class MainWindow : Window     {         public MainWindow()         {             InitializeComponent();         }     } } 
like image 63
Wiesław Šoltés Avatar answered Oct 10 '22 13:10

Wiesław Šoltés


The way I solved this problem was to place the image within a Border with it's ClipToBounds property set to True. The RenderTransformOrigin on the image is then set to 0.5,0.5 so the image will start zooming on the center of the image. The RenderTransform is also set to a TransformGroup containing a ScaleTransform and a TranslateTransform.

I then handled the MouseWheel event on the image to implement zooming

private void image_MouseWheel(object sender, MouseWheelEventArgs e) {     var st = (ScaleTransform)image.RenderTransform;     double zoom = e.Delta > 0 ? .2 : -.2;     st.ScaleX += zoom;     st.ScaleY += zoom; } 

To handle the panning the first thing I did was to handle the MouseLeftButtonDown event on the image, to capture the mouse and to record it's location, I also store the current value of the TranslateTransform, this what is updated to implement panning.

Point start; Point origin; private void image_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {     image.CaptureMouse();     var tt = (TranslateTransform)((TransformGroup)image.RenderTransform)         .Children.First(tr => tr is TranslateTransform);     start = e.GetPosition(border);     origin = new Point(tt.X, tt.Y); } 

Then I handled the MouseMove event to update the TranslateTransform.

private void image_MouseMove(object sender, MouseEventArgs e) {     if (image.IsMouseCaptured)     {         var tt = (TranslateTransform)((TransformGroup)image.RenderTransform)             .Children.First(tr => tr is TranslateTransform);         Vector v = start - e.GetPosition(border);         tt.X = origin.X - v.X;         tt.Y = origin.Y - v.Y;     } } 

Finally don't forget to release the mouse capture.

private void image_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) {     image.ReleaseMouseCapture(); } 

As for the selection handles for resizing this can be accomplished using an adorner, check out this article for more information.

like image 21
Ian Oakes Avatar answered Oct 10 '22 11:10

Ian Oakes