我希望有人可以帮助我 . 我正在构建一个WPF成像应用程序,它从相机中获取实时图像,允许用户查看图像,然后突出显示该图像上的感兴趣区域(ROI) . 然后将关于ROI的信息(宽度,高度,相对于图像上的点的位置等)发送回相机,实际上告知/训练相机固件在哪里寻找条形码,文本,液位,转弯等内容在图像上的螺丝等) . 所需的功能是能够平移和缩放图像及其ROI,以及在图像缩放比观看区域大时滚动 . ROI的StrokeThickness和FontSize需要保持原始比例,但ROI内形状的宽度和高度需要与图像一起缩放(这对于捕获传输到相机的精确像素位置至关重要) . 除了滚动和其他一些问题之外,我已经解决了大部分问题 . 我关注的两个方面是:
-
当我介绍ScrollViewer时,我没有得到任何滚动行为 . 据我所知,我需要引入一个LayoutTransform来获得正确的ScrollViewer行为 . 然而,当我这样做时,其他区域开始崩溃(例如,ROI在图像上没有保持正确的位置,或者当平移时鼠标指针开始从图像上的选定点开始蠕动,或者我的图像的左角弹回到MouseDown上的当前鼠标位置 . )
-
我无法按照我需要的方式调整投资回报率 . 我有这个工作,但它并不理想 . 我所拥有的并没有保留精确的笔划厚度,我没有考虑忽略文本块上的比例 . 希望你能看到我在代码示例中正在做的事情 .
我确定我的问题与我对Transforms及其与WPF布局系统的关系缺乏了解有关 . 希望展示我迄今为止所取得成就的代码将有所帮助(见下文) .
仅供参考,如果Adorners是建议,可能在我的场景中不起作用,因为我最终会得到比支持更多的装饰(谣言144装饰者是事情开始崩溃的时候) .
首先,下面是显示带有ROI(文本和形状)的图像的屏幕截图 . 矩形,椭圆和文本需要按照比例和旋转方式跟随图像上的区域,但不能在厚度或字体大小上进行缩放 .
Here's the XAML that is showing the above image, along with a Slider for zooming (mousewheel zoom will come later)
<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>
Here's the C# that manages pan and zoom.
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项目和解决方案中工作 .
有什么想法吗?欢迎提出建议 .
2 回答
好 . 这是我对你描述的内容的看法 .
它看起来像这样:
由于我没有应用任何
RenderTransforms
,因此我获得了所需的Scrollbar / ScrollViewer功能 .MVVM,这是WPF的方法 . UI和数据是独立的,因此DataItems只有X,Y,Width,Height等的
double
和int
属性,您可以将它们用于任何目的,甚至将它们存储在数据库中 .我在
Thumb
中添加了所有内容来处理平移 . 当您通过ResizerControl拖动/调整ROI大小时,您仍需要对平移进行一些操作 . 我想你可以检查Mouse.DirectlyOver
或其他什么 .我实际上使用了
ListBox
来处理投资回报率,这样您在任何给定时间都可以获得1个选定的投资回报率 . 这会切换调整大小功能 . 因此,如果您点击ROI,您将看到缩放器可见 .Scaling在ViewModel级别处理,因此无需自定义
Panels
或类似的东西(尽管@Clemens的解决方案也很好)我正在使用
Enum
和一些DataTriggers
来定义形状 . 请参阅DataTemplate DataType={x:Type local:ROI}
部分 .WPF Rocks . 只需将我的代码复制并粘贴到
File -> New Project -> WPF Application
中,然后自行查看结果 .代码背后:
主视图模型:
ROI ViewModel:
形状枚举:
PropertyChangedBase(MVVM Helper类):
调整器控制:
代码背后:
为了在不改变笔触粗细的情况下变换形状,可以使用具有变换几何的
Path
对象 .以下XAML在Canvas上放置一个Image和两个Path . 图像由RenderTransform缩放和翻译 . 相同的变换也用于几何的Transform属性两条路径 .
您的应用程序现在可以简单地更改
transform
对象以响应MouseMove或MouseWheel等输入事件 .在转换TextBlocks或其他不应缩放的元素时,事情变得有点棘手,但只能移动到正确的位置 .
您可以创建一个专门的Panel,它能够将这种变换应用于其子元素 . 这样的Panel将定义一个控制子元素位置的附加属性,并将变换应用于此位置而不是子项的
RenderTransform
或LayoutTransform
.这可以让您了解如何实施这样一个小组:
它将在XAML中使用,如下所示: