WPF Image Przesuwaj, powiększaj i przewijaj z warstwami na płótnie

Mam nadzieję, że ktoś może mi pomóc tutaj. Buduję aplikację do obrazowania WPF, która pobiera obrazy na żywo z kamery, umożliwiając użytkownikom oglądanie obrazu, a następnie wyróżnianie obszarów zainteresowania (ROI) na tym obrazie. Informacje o ROI (szerokość, wysokość, położenie względem punktu na obrazie itp.) Są następnie przesyłane z powrotem do aparatu, w efekcie mówiąc / szkoląc oprogramowanie aparatu, gdzie szukać rzeczy, takich jak kody kreskowe, tekst, poziomy cieczy, zakręty na śrubie itp. na obrazie). Pożądaną cechą jest możliwość przesuwania i powiększania obrazu i jego ROI, a także przewijania, gdy obraz jest większy niż obszar wyświetlania. StrokeThickness i FontSize ROI muszą zachować pierwotną skalę, ale szerokość i wysokość kształtów w obrębie ROI muszą być skalowane wraz z obrazem (jest to niezbędne do uchwycenia dokładnych lokalizacji pikseli do przesłania do kamery). Udało mi się to większość z wyjątkiem przewijania i kilku innych problemów. Moje dwa obszary zainteresowania to:

Kiedy wprowadzam ScrollViewer, nie otrzymuję żadnego zachowania przewijania. Jak rozumiem, muszę wprowadzić LayoutTransform, aby uzyskać prawidłowe zachowanie ScrollViewer. Jednak kiedy to robię, inne obszary zaczynają się załamywać (np. ROI nie zachowują prawidłowej pozycji nad obrazem lub wskaźnik myszy zaczyna pełzać z wybranego punktu na obrazie podczas panoramowania lub w lewym rogu mojego obrazu) odbija się do bieżącej pozycji myszy na MouseDown.)

Nie mogę uzyskać skalowania mojego ROI tak, jak ich potrzebuję. Mam to działa, ale nie jest idealne. To, co mam, nie zachowuje dokładnej grubości obrysu i nie patrzyłem na ignorowanie skali na blokach tekstowych. Mam nadzieję, że zobaczysz, co robię w przykładach kodu.

Jestem pewien, że mój problem ma coś wspólnego z moim brakiem zrozumienia transformacji i ich związku z układem WPF. Mam nadzieję, że interpretacja kodu, który pokazuje, co osiągnąłem do tej pory, pomoże (patrz poniżej).

FYI, jeśli Adorners są sugestią, to może nie zadziałać w moim scenariuszu, ponieważ mógłbym skończyć z większą ilością ozdób niż jest obsługiwana (plotka 144 ozdób jest wtedy, gdy wszystko zaczyna się załamywać).

Po pierwsze, poniżej znajduje się zrzut ekranu przedstawiający obraz do ROI (tekst i kształt). Prostokąt, elipsa i tekst muszą podążać za obszarem na obrazie w skali i obrocie, ale nie powinny być skalowane w grubości lub rozmiarze czcionki.

Oto XAML, który pokazuje powyższy obraz, wraz z suwakiem do powiększania (powiększenie kółka myszy nastąpi później)

<Window x:Class="PanZoomStackOverflow.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    mc:Ignorable="d"
    Title="MainWindow" Height="768" Width="1024">

<DockPanel>
  <Slider x:Name="_ImageZoomSlider" DockPanel.Dock="Bottom"
          Value="2"
          HorizontalAlignment="Center" Margin="6,0,0,0" 
          Width="143" Minimum=".5" Maximum="20" SmallChange=".1" 
          LargeChange=".2" TickFrequency="2" 
          TickPlacement="BottomRight" Padding="0" Height="23"/>

  <!-- This resides in a user control in my solution -->
  <Grid x:Name="LayoutRoot">
    <ScrollViewer Name="border" HorizontalScrollBarVisibility="Auto" 
                  VerticalScrollBarVisibility="Auto">
      <Grid x:Name="_ImageDisplayGrid">
        <Image x:Name="_DisplayImage" Margin="2" Stretch="None"
               Source="Untitled.bmp"
               RenderTransformOrigin ="0.5,0.5"
               RenderOptions.BitmapScalingMode="NearestNeighbor"
               MouseLeftButtonDown="ImageScrollArea_MouseLeftButtonDown"
               MouseLeftButtonUp="ImageScrollArea_MouseLeftButtonUp"
               MouseMove="ImageScrollArea_MouseMove">                            
           <Image.LayoutTransform>
             <TransformGroup>
               <ScaleTransform />
               <TranslateTransform />
             </TransformGroup>
           </Image.LayoutTransform>
         </Image>
         <AdornerDecorator> <!-- Using this Adorner Decorator for Move, Resize and Rotation and feedback adornernments -->
           <Canvas x:Name="_ROICollectionCanvas"
                   Width="{Binding ElementName=_DisplayImage, Path=ActualWidth, Mode=OneWay}"
                   Height="{Binding ElementName=_DisplayImage, Path=ActualHeight, Mode=OneWay}"
                   Margin="{Binding ElementName=_DisplayImage, Path=Margin, Mode=OneWay}">

             <!-- This is a user control in my solution -->
             <Grid IsHitTestVisible="False" Canvas.Left="138" Canvas.Top="58" Height="25" Width="186">
               <TextBlock Text="Rectangle ROI" HorizontalAlignment="Center" VerticalAlignment="Top" 
                          Foreground="Orange" FontWeight="Bold" Margin="0,-15,0,0"/>
                 <Rectangle StrokeThickness="2" Stroke="Orange"/>
             </Grid>

             <!-- This is a user control in my solution -->
             <Grid IsHitTestVisible="False" Canvas.Left="176" Canvas.Top="154" Height="65" Width="69">
               <TextBlock Text="Ellipse ROI" HorizontalAlignment="Center" VerticalAlignment="Top" 
                          Foreground="Orange" FontWeight="Bold" Margin="0,-15,0,0"/>
               <Ellipse StrokeThickness="2" Stroke="Orange"/>
             </Grid>
           </Canvas>
         </AdornerDecorator>
       </Grid>
     </ScrollViewer>
  </Grid>
</DockPanel>

Oto C #, który zarządza panoramowaniem i powiększaniem.

public partial class MainWindow : Window
{
private Point origin;
private Point start;
private Slider _slider;

public MainWindow()
{
    this.InitializeComponent();

    //Setup a transform group that we'll use to manage panning of the image area
    TransformGroup group = new TransformGroup();
    ScaleTransform st = new ScaleTransform();
    group.Children.Add(st);
    TranslateTransform tt = new TranslateTransform();
    group.Children.Add(tt);
    //Wire up the slider to the image for zooming
    _slider = _ImageZoomSlider;
    _slider.ValueChanged += _ImageZoomSlider_ValueChanged;
    st.ScaleX = _slider.Value;
    st.ScaleY = _slider.Value;
    //_ImageScrollArea.RenderTransformOrigin = new Point(0.5, 0.5);
    //_ImageScrollArea.LayoutTransform = group;
    _DisplayImage.RenderTransformOrigin = new Point(0.5, 0.5);
    _DisplayImage.RenderTransform = group;
    _ROICollectionCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
    _ROICollectionCanvas.RenderTransform = group;
}

//Captures the mouse to prepare for panning the scrollable image area
private void ImageScrollArea_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    _DisplayImage.ReleaseMouseCapture();
}

//Moves/Pans the scrollable image area  assuming mouse is captured.
private void ImageScrollArea_MouseMove(object sender, MouseEventArgs e)
{
    if (!_DisplayImage.IsMouseCaptured) return;

    var tt = (TranslateTransform)((TransformGroup)_DisplayImage.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;
}

//Cleanup for Move/Pan when mouse is released
private void ImageScrollArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    _DisplayImage.CaptureMouse();
    var tt = (TranslateTransform)((TransformGroup)_DisplayImage.RenderTransform).Children.First(tr => tr is TranslateTransform);
    start = e.GetPosition(border);
    origin = new Point(tt.X, tt.Y);
}

//Zoom according to the slider changes
private void _ImageZoomSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
    //Panel panel = _ImageScrollArea;
    Image panel = _DisplayImage;

    //Set the scale coordinates on the ScaleTransform from the slider
    ScaleTransform transform = (ScaleTransform)((TransformGroup)panel.RenderTransform).Children.First(tr => tr is ScaleTransform);
    transform.ScaleX = _slider.Value;
    transform.ScaleY = _slider.Value;


    //Set the zoom (this will affect rotate too) origin to the center of the panel
    panel.RenderTransformOrigin = new Point(0.5, 0.5);

    foreach (UIElement child in _ROICollectionCanvas.Children)
    {
        //Assume all shapes are contained in a panel
        Panel childPanel = child as Panel;

        var x = childPanel.Children;

        //Shape width and heigh should scale, but not StrokeThickness
        foreach (var shape in childPanel.Children.OfType<Shape>())
        {
            if (shape.Tag == null)
            {
                //Hack: This is be a property on a usercontrol in my solution
                shape.Tag = shape.StrokeThickness;
            }
            double orignalStrokeThickness = (double)shape.Tag;

            //Attempt to keep the underlying shape border/stroke from thickening as well
            double newThickness = shape.StrokeThickness - (orignalStrokeThickness / transform.ScaleX);

            shape.StrokeThickness -= newThickness;
        }
    }
}
}

Kod powinien działać w projekcie i rozwiązaniu .NET 4.0 lub 4.5, zakładając brak błędów wycinania / wklejania.

jakieś pomysły? Sugestie są mile widziane.

questionAnswers(2)

yourAnswerToTheQuestion