title: Calculate a ScrollViewer's scrollbar offsets so that an object will be centered, while maintaining a layout transform who's scale is variable
body: <p>I have been fighting with this one for many hours now, and I just can't seem to arrive at an acceptable answer.  I am hoping someone out there with much stronger geometry skills than my self can solve this riddle for me.   Any help would be greatly appreciated.   The nature of my problem, and description is below in the image that I provided.</p>
  
  <p><a href="https://i.stack.imgur.com/tcAnU.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/tcAnU.png" alt="enter image description here"></a></p>
  
  <p>And here is a sample project that I have built, which does not correctly fulfill the requirements. </p>
  
  <p><strong>XAML:</strong></p>
  
  <pre><code>&lt;Window x:Class="MainWindow"
      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"
      Title="Center and Zoom ScrollViewer Test" Height="600" Width="800" WindowStartupLocation="CenterScreen"&gt;
  &lt;Grid&gt;
      &lt;DockPanel&gt;
          &lt;GroupBox Header="Parameters" DockPanel.Dock="Top" Margin="10"&gt;
              &lt;StackPanel Orientation="Horizontal"&gt;
                  &lt;GroupBox Header="Manually Set ScrollBar Positions" Margin="10"&gt;
                      &lt;StackPanel Orientation="Horizontal"&gt;
                          &lt;TextBox Name="EditHorz" Width="60" Margin="10" TextChanged="EditHorz_TextChanged" /&gt;
                          &lt;Label Content="x" Margin="0 10 0 10" /&gt;
                          &lt;TextBox Name="EditVert" Width="60" Margin="10" TextChanged="EditVert_TextChanged" /&gt;
                      &lt;/StackPanel&gt;
                  &lt;/GroupBox&gt;
  
                  &lt;GroupBox Header="Scale" Margin="10"&gt;
                      &lt;DockPanel&gt;
                          &lt;Label Content="{Binding ElementName=scaleValue, Path=Value}" DockPanel.Dock="Right" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Width="40" /&gt;
                          &lt;Slider Name="scaleValue" Minimum="1" Maximum="4" SmallChange="0.05" LargeChange="0.1" Width="200" VerticalAlignment="Center" /&gt;
                      &lt;/DockPanel&gt;
                  &lt;/GroupBox&gt;
              &lt;/StackPanel&gt;
          &lt;/GroupBox&gt;
  
          &lt;GroupBox Header="Debug Output" Margin="10"&gt;
              &lt;TextBox Name="text" FontFamily="Courier New" FontSize="12" DockPanel.Dock="Left" Width="500" TextWrapping="Wrap" AcceptsReturn="True" AcceptsTab="True" Margin="10" /&gt;
          &lt;/GroupBox&gt;
  
          &lt;GroupBox Header="Proof" Margin="10"&gt;
              &lt;DockPanel&gt;
                  &lt;StackPanel Orientation="Horizontal" DockPanel.Dock="Bottom" HorizontalAlignment="Center"&gt;
                      &lt;Button Width="60" HorizontalAlignment="Left" Content="Center" Click="ButtonCenter_Click" Margin="10" /&gt;
                      &lt;Button Width="60" HorizontalAlignment="Left" Content="Reset" Click="ButtonReset_Click" Margin="10" /&gt;
                  &lt;/StackPanel&gt;
                  &lt;ScrollViewer Name="scroll" VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden" Background="Green" Width="100" Height="100" VerticalAlignment="Top" Margin="10"&gt;
                      &lt;Canvas Width="200" Height="200" Background="Red"&gt;
                          &lt;Canvas.LayoutTransform&gt;
                              &lt;ScaleTransform ScaleX="{Binding ElementName=scaleValue, Path=Value}" ScaleY="{Binding ElementName=scaleValue, Path=Value}" /&gt;
                          &lt;/Canvas.LayoutTransform&gt;
                          &lt;Rectangle Name="rect" Width="40" Height="40" Canvas.Left="120" Canvas.Top="70" Fill="Blue" /&gt;
                      &lt;/Canvas&gt;
                  &lt;/ScrollViewer&gt;
              &lt;/DockPanel&gt;
          &lt;/GroupBox&gt;
      &lt;/DockPanel&gt;
  &lt;/Grid&gt;
  </code></pre>
  
  <p></p>
  
  <p><strong>Code Behind (VB.net)</strong></p>
  
  <pre><code>Class MainWindow
  ''' Calculates the horizontal and vertical scrollbar offsets so that
  ''' the blue rectangle is centered within the scroll viewer.
  Private Sub RecalculateCenter()
      ' the scale we are using
      Dim scale As Double = scaleValue.Value
  
      ' get the rectangles current position within the canvas
      Dim rectLeft As Double = Canvas.GetLeft(rect)
      Dim rectTop As Double = Canvas.GetTop(rect)
  
      ' set our point of interest "Rect" equal to the the whole coordinates of the rectangle
      Dim poi As Rect = New Rect(rectLeft, rectTop, rect.Width, rect.Height)
  
      ' get our view offset
      Dim ofsViewWidth As Double = (scroll.ScrollableWidth - (((scroll.ViewportWidth / 2) - (rect.ActualWidth / 2)) * scale)) / scale
      Dim ofsViewHeight As Double = (scroll.ScrollableHeight - (((scroll.ViewportHeight / 2) - (rect.ActualHeight / 2)) * scale)) / scale
  
      ' calculate our scroll bar offsets
      Dim verticalOffset As Double = (poi.Top - ofsViewHeight) * scale
      Dim horizontalOffset As Double = (poi.Left - ofsViewWidth) * scale
  
      ' record the output to the debug output window
      Dim sb As New StringBuilder()
      sb.AppendLine($"Scale      : {scale}")
      sb.AppendLine($"POI        : {poi.ToString()}")
      sb.AppendLine($"Rect       : {rectLeft}x{rectTop}")
      sb.AppendLine($"Extent     : {scroll.ExtentWidth}x{scroll.ExtentHeight}")
      sb.AppendLine($"Scrollable : {scroll.ScrollableWidth}x{scroll.ScrollableHeight}")
      sb.AppendLine($"View Offset: {ofsViewWidth}x{ofsViewHeight}")
      sb.AppendLine($"Horizontal : {horizontalOffset}")
      sb.AppendLine($"Vertical   : {verticalOffset}")
  
      text.Text = sb.ToString()
  
      ' set the EditHorz and EditVert text box values, this will trigger the scroll
      ' bar offsets to fire via the TextChanged event handlers
      EditHorz.Text = horizontalOffset.ToString()
      EditVert.Text = verticalOffset.ToString()
  End Sub
  
  ''' Try and parse the horizontal text box to a double, and set the scroll bar position accordingly
  Private Sub SetScrollBarHorizontalOffset()
      Dim ofs As Double = 0
      If Double.TryParse(EditHorz.Text, ofs) Then
          scroll.ScrollToHorizontalOffset(ofs)
      End If
  End Sub
  
  ''' Try and parse the vertical text box to a double, and set the scroll bar position accordingly
  Private Sub SetScrollBarVerticalOffset()
      Dim ofs As Double = 0
      ofs = 0
      If Double.TryParse(EditVert.Text, ofs) Then
          scroll.ScrollToVerticalOffset(ofs)
      End If
  End Sub
  
  ''' Parse and set scrollbars positions for both Horizontal and Vertical
  Private Sub SetScrollBarOffsets()
      SetScrollBarHorizontalOffset()
      SetScrollBarVerticalOffset()
  End Sub
  
  Private Sub ButtonCenter_Click(sender As Object, e As RoutedEventArgs)
      RecalculateCenter()
  End Sub
  
  Private Sub ButtonReset_Click(sender As Object, e As RoutedEventArgs)
      scroll.ScrollToVerticalOffset(0)
      scroll.ScrollToHorizontalOffset(0)
  End Sub
  
  Private Sub EditHorz_TextChanged(sender As Object, e As TextChangedEventArgs)
      SetScrollBarOffsets()
  End Sub
  
  Private Sub EditVert_TextChanged(sender As Object, e As TextChangedEventArgs)
      SetScrollBarOffsets()
  End Sub
  </code></pre>
  
  <p>End Class</p>