Scale other content on top of a Deep Zoom Image

Marthinus asked, in a comment:

“is there ANY way to overwrite this, so that I can make the msi use a multiscalesubimage WITH an extra canvas ontop (which would then move and scale WITH the subimage)?”

Now, I’ve never done overlays with collections and sub-images, and one thing you definitely can’t do is interleave other content with subimages of a deep zoom image (since the MultiScaleImage is a single UI element). But I have done things which have locked an overlay panel to the movement of the MultiScaleImage. Here’s a sample. First, the Xaml.


<UserControl x:Class="DeepZoomCanvas.MainPage"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
 mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
 <Grid x:Name="LayoutRoot">
 <MultiScaleImage x:Name="msi" Source="http://www.uslot.com/ClientBin/dzc_output.xml"/>
 <Grid 
 IsHitTestVisible="False"
 x:Name="overlay">
 <Grid.RenderTransform>
 <TransformGroup>
 <ScaleTransform x:Name="overlayScale"/>
 <TranslateTransform x:Name="overlayTranslate"/>
 </TransformGroup>
 </Grid.RenderTransform>
 <Border 
 CornerRadius="8" 
 Background="#44FFFFFF" 
 VerticalAlignment="Center" 
 HorizontalAlignment="Center" 
 Padding="30">
 <StackPanel>
 <TextBlock 
 FontFamily="Arial" 
 FontSize="24" 
 MaxWidth="400" 
 FontWeight="Bold" 
 TextWrapping="Wrap">
 This shows some elements which 
 scale and translate locked to 
 the underlying MultiScaleImage. 
 Any layout items can be used here.
 </TextBlock>
 <Border Background="#55FF0000" Padding="10" HorizontalAlignment="Center" VerticalAlignment="Top">
 <StackPanel>
 <TextBlock 
 FontFamily="Arial"
 FontSize="0.1"
 >This is a tiny line of text which still scales in concert with the deep zoom image.</TextBlock>
 </StackPanel>
 </Border>
 </StackPanel>
 </Border>
 </Grid>
 </Grid>
</UserControl>

And the corresponding C# code:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace DeepZoomCanvas
{
 public partial class MainPage : UserControl
 {
 public MainPage()
 {
 InitializeComponent();
 msi.MouseLeftButtonDown += new MouseButtonEventHandler(msi_MouseLeftButtonDown);
 msi.MouseMove += new MouseEventHandler(msi_MouseMove);
 msi.MouseLeftButtonUp += new MouseButtonEventHandler(msi_MouseLeftButtonUp);
 msi.MouseWheel += new MouseWheelEventHandler(msi_MouseWheel);

 // Track changes to the multi scale image
 msi.ViewportChanged += new RoutedEventHandler(msi_ViewportChanged);
 }

 /// <summary>
 /// This is the code which locks the overlay to the underlying deep zoom image.
 /// All it really does is set the scale factor and offset of the overlay
 /// based on the current setting of the deep zoom image.
 /// </summary>
 ///
<param name="sender">event sender</param>
 ///
<param name="e">event args</param>
 void msi_ViewportChanged(object sender, RoutedEventArgs e)
 {
 // This event is called during animations of the image.
 // Match the scaling of the canvas with the image
 Point viewportOrigin = msi.ViewportOrigin;
 double viewportWidth = msi.ViewportWidth;

 // The scale factor is just the inverse of the ViewportWidth
 overlayScale.ScaleX = 1 / viewportWidth;
 overlayScale.ScaleY = 1 / viewportWidth;

 // The offset is calculated by finding the location of the origin of the dzi
 // in element coordinates.
 Point newO = LogicalToElement(new Point(), viewportOrigin, viewportWidth);
 overlayTranslate.X = newO.X;
 overlayTranslate.Y = newO.Y;
 }

 private Point LogicalToElement(Point p, Point Origin, double Width)
 {
 return new Point(((p.X - Origin.X) / Width) * msi.ActualWidth,
 ((p.Y - Origin.Y) / Width) * msi.ActualWidth);
 }

 public Point ElementToLogical(Point p, Point Origin, double Width)
 {
 return new Point(Origin.X + (p.X * Width) / msi.ActualWidth,
 Origin.Y + (p.Y * Width) / msi.ActualWidth);
 }

#region Mouse handling
 void msi_MouseWheel(object sender, MouseWheelEventArgs e)
 {
 if (e.Delta < 0)
 {
 Point logicalPoint = msi.ElementToLogicalPoint(lastMousePos);
 msi.ZoomAboutLogicalPoint(0.8, logicalPoint.X, logicalPoint.Y);

 }
 else
 {
 Point logicalPoint = msi.ElementToLogicalPoint(lastMousePos);
 msi.ZoomAboutLogicalPoint(1.2, logicalPoint.X, logicalPoint.Y);
 }
 }

 void msi_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
 {
 if (IsMouseDown)
 {
 msi.ReleaseMouseCapture();
 if (IsDrag == false)
 {
 if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
 {
 Point logicalPoint = msi.ElementToLogicalPoint(lastMousePos);
 msi.ZoomAboutLogicalPoint(0.8, logicalPoint.X, logicalPoint.Y);
 }
 else
 {
 Point logicalPoint = msi.ElementToLogicalPoint(lastMousePos);
 msi.ZoomAboutLogicalPoint(1.2, logicalPoint.X, logicalPoint.Y);
 }
 }
 IsMouseDown = false;
 IsDrag = false;
 }
 }

 void msi_MouseMove(object sender, MouseEventArgs e)
 {
 lastMousePos = e.GetPosition(msi);
 if (IsMouseDown)
 {
 IsDrag = true;
 }
 if (IsDrag)
 {
 Point newPoint = lastMouseViewPort;
 newPoint.X += (lastMouseDownPos.X - lastMousePos.X) / msi.ActualWidth * msi.ViewportWidth;
 newPoint.Y += (lastMouseDownPos.Y - lastMousePos.Y) / msi.ActualWidth * msi.ViewportWidth;
 msi.ViewportOrigin = newPoint;
 }
 }

 bool IsMouseDown = false;
 bool IsDrag = false;
 private Point lastMouseDownPos = new Point();
 private Point lastMouseViewPort = new Point();
 private Point lastMousePos = new Point();

 void msi_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
 {
 IsMouseDown = true;
 msi.CaptureMouse();
 lastMouseDownPos = e.GetPosition(msi);
 lastMouseViewPort = msi.ViewportOrigin;
 }

 #endregion    
 }
}

All the work is done in the ViewportChanged event handler (which fires every time the viewport changes, even during animations). I’ve set a scale transform and a translate transform on the grid (which could be a canvas if you want) and we adjust the scale and translate values to match the underlying image.

All the rest of the code is just bog-standard deep zoom image mouse handling.

Try zooming into the red square to see a very small line of text.

Hope this helps.

Advertisements

6 comments

  1. This works great to scale and move items on top of a multiscaleimage until the size of the msi or canvas changes…for example, going full screen. Any ideas on how to handle that scenario?

    1. Hi David,
      I try to solve the problem adding this code.
      It works fine when I resize the window or put it full screen but it doesn’t works when I moved the multiscaleimage. I haven’t figure out the reason.


      public MainPage()
      { ...
      msi.SizeChanged += new SizeChangedEventHandler(msi_SizeChanged);
      ....
      }

      void msi_SizeChanged(object sender, SizeChangedEventArgs e)
      {
      if (e.PreviousSize.Width == 0)
      {
      //// initial factor
      double factor = e.NewSize.Width / 1260; //initial size of MultiscaleImage
      //// The scale factor is just the inverse of the ViewportWidth
      overlayScale.ScaleX = overlayScale.ScaleX * factor;
      overlayScale.ScaleY = overlayScale.ScaleY * factor;
      OriginalScaleX = overlayScale.ScaleX;
      OriginalScaleY = overlayScale.ScaleY;
      }

      if (e.PreviousSize.Width > 0)
      {
      //// factor to resize the overlay grid when window resized
      double factor = e.NewSize.Width / e.PreviousSize.Width;
      //// This event is called during animations of the image.
      //// Match the scaling of the canvas with the image
      Point viewportOrigin = MSI.ViewportOrigin;
      double viewportWidth = MSI.ViewportWidth;

      //// The scale factor is just the inverse of the ViewportWidth
      overlayScale.ScaleX = overlayScale.ScaleX * factor;
      overlayScale.ScaleY = overlayScale.ScaleY * factor;
      OriginalScaleX = overlayScale.ScaleX;
      OriginalScaleY = overlayScale.ScaleY;

      //// The offset is calculated by finding the location of the origin of the dzi
      //// in element coordinates.
      Point newO = LogicalToElement(new Point(), viewportOrigin, viewportWidth);
      overlayTranslate.X = newO.X;
      overlayTranslate.Y = newO.Y;
      }
      }

  2. I have been trying out how to make the box scale appropriately if the msi has already been zoomed on but I can’t quite crack it. That is, no matter where the user is in browsing the deepzoom project, if they click a static button, it will draw a box around a subimage of the collection and then continue to scale while they continue to zoom in and out.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s