WPF-Bild schwenken, zoomen und scrollen mit Ebenen auf einer Leinwand
Ich hoffe, hier kann mir jemand helfen. Ich erstelle eine WPF-Bildbearbeitungsanwendung, die Livebilder von einer Kamera aufnimmt, sodass Benutzer das Bild anzeigen und anschließend Bereiche von Interesse (ROI) auf diesem Bild markieren können. Informationen zu den ROIs (Breite, Höhe, Position in Bezug auf einen Punkt im Bild usw.) werden dann an die Kamera zurückgesendet. Auf diese Weise wird der Kamera-Firmware mitgeteilt / trainiert, wo nach Dingen wie Barcodes, Text, Flüssigkeitsständen und Abbiegungen gesucht werden soll auf einer Schraube usw. auf dem Bild). Eine gewünschte Funktion ist das Schwenken und Zoomen des Bildes und seiner ROIs sowie das Scrollen, wenn das Bild größer als der Anzeigebereich gezoomt wird. Die Strichstärke und die Schriftgröße der ROIs müssen ihren ursprünglichen Maßstab beibehalten, aber die Breite und Höhe der Formen innerhalb einer ROIs müssen mit dem Bild skaliert werden (dies ist wichtig, um genaue Pixelpositionen zu erfassen, die an die Kamera übertragen werden sollen). Ich habe das meiste davon abgesehen vom Scrollen und ein paar anderen Problemen herausgefunden. Meine beiden Anliegen sind:
Wenn ich einen ScrollViewer einführe, bekomme ich kein Scrollverhalten. Nach meinem Verständnis muss ich eine LayoutTransform einführen, um das richtige ScrollViewer-Verhalten zu erzielen. Wenn ich dies jedoch tue, brechen andere Bereiche zusammen (z. B. halten die ROIs nicht die richtige Position über dem Bild, oder der Mauszeiger beginnt beim Schwenken vom ausgewählten Punkt auf dem Bild oder der linken Ecke meines Bildes wegzukriechen springt auf die aktuelle Mausposition auf MouseDown.)
Ich kann die Skalierung meines ROI nicht so erreichen, wie ich sie brauche. Ich habe diese Arbeit, aber es ist nicht ideal. Was ich habe, behält nicht die exakte Strichstärke bei, und ich habe mich nicht darum gekümmert, die Skalierung auf den Textblöcken zu ignorieren. Hoffentlich sehen Sie, was ich in den Codebeispielen mache.
Ich bin mir sicher, dass mein Problem damit zu tun hat, dass ich Transformationen und deren Beziehung zum WPF-Layoutsystem nicht verstehe. Hoffentlich hilft eine Wiedergabe des Codes, der das zeigt, was ich bisher erreicht habe (siehe unten).
Zu Ihrer Information, wenn Adorners der Vorschlag sind, könnte dies in meinem Szenario nicht funktionieren, da ich möglicherweise mehr Adorners als unterstützt habe (Gerücht 144 Adorners ist, wenn die Dinge anfangen zusammenzubrechen).
Zunächst ist unten ein Screenshot zu sehen, der ein Bild mit den ROIs (Text und Form) zeigt. Das Rechteck, die Ellipse und der Text müssen dem Bildbereich in Bezug auf Skalierung und Drehung folgen, nicht jedoch in Bezug auf Stärke oder Schriftgröße.
Hier ist die XAML, die das obige Bild zeigt, zusammen mit einem Schieberegler zum Zoomen (das Zoomen mit dem Mausrad erfolgt später).
<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>
Hier ist das C #, das das Schwenken und Zoomen verwaltet.
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;
}
}
}
}
Der Code sollte in einem .NET 4.0- oder 4.5-Projekt und einer -Lösung funktionieren, sofern keine Fehler beim Ausschneiden / Einfügen vorliegen.
Irgendwelche Gedanken? Vorschläge sind willkommen.