Я надеюсь, что кто-то может помочь мне здесь. Я создаю приложение для визуализации WPF, которое отображает живые изображения с камеры, позволяя пользователям просматривать изображение, а затем выделять интересующие области (ROI) на этом изображении. Информация о ROI (ширина, высота, местоположение относительно точки на изображении и т.д.) Затем отправляется обратно на камеру, в основном говоря/обучая прошивку камеры, где нужно искать такие вещи, как штрих-коды, текст, уровни жидкости, повороты на винт и т.д. на изображении). Желательной особенностью является возможность панорамирования и масштабирования изображения и его ROI, а также прокрутка, когда изображение увеличено больше, чем область просмотра. Для StrokeThickness и FontSize для ROI необходимо сохранить исходный масштаб, но ширина и высота фигур в пределах ROI должны масштабироваться с изображением (это важно для захвата точных местоположений пикселей для передачи на камеру). У меня есть большая часть этой работы, за исключением прокрутки и нескольких других вопросов. Мои две проблемы:
-
Когда я представляю ScrollViewer, я не получаю никакого поведения прокрутки. Насколько я понимаю, мне нужно ввести LayoutTransform, чтобы получить правильное поведение ScrollViewer. Однако, когда я делаю это, другие области начинают разрушаться (например, ROI не занимают правильное положение над изображением, или указатель мыши начинает ползти от выбранной точки на изображении при панорамировании или в левом углу моего изображения отскакивает до текущей позиции мыши на MouseDown.)
-
Я не могу получить масштабирование своей ROI так, как мне это нужно. У меня это работает, но это не идеально. То, что у меня есть, не сохраняет точную толщину удара, и я не смотрел на игнорирование шкалы на текстовых блоках. Надеюсь, вы увидите, что я делаю в образцах кода.
Я уверен, что моя проблема связана с моим недостатком понимания Transforms и их отношением к системе компоновки WPF. Надеемся, что реализация кода, который показывает то, что я сделал до сих пор, поможет (см. Ниже).
FYI, если Adorners - это предложение, которое может не работать в моем сценарии, потому что я мог бы получить больше украшений, чем поддерживаться (слухи 144 adorners - это когда все начинает разрушаться).
Прежде всего, снизу показан снимок с изображением с ROI (текст и форма). Прямоугольник, эллипс и текст должны следить за областью изображения по масштабу и вращению, но не должны масштабироваться по толщине или шрифту.
Здесь XAML, показывающий вышеприведенное изображение, и слайдер для масштабирования (увеличение масштаба мыши) будет
<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>
Здесь С#, который управляет панорамированием и масштабированием.
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;
}
}
}
}
Код должен работать в проекте и решении .NET 4.0 или 4.5, не допуская ошибок вырезания/вставки.
Любые мысли? Предложения приветствуются.