What's the best way for implementing Zoom (possibly with Pinch) and Move (possibly with for Slide) for a Canvas?
I'm drawing some very simple stuff (e.g Lines, Ellipses and more) on a Canvas and now I want to allow the user the Zoom-in, Zoom-out and move the view-port freely around.
here you go. in the XAML code, wrap it with scroll viewer. like this
<ScrollViewer x:Name="scrl" ZoomMode="Enabled" HorizontalScrollMode="Enabled" VerticalScrollMode="Enabled" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible" SizeChanged="OnSizeChanged" MinZoomFactor="1">
<Canvas Background="AliceBlue" RenderTransformOrigin="0.5,0.5" x:Name="Main">
<Image Source="Assets/Floorplan.gif" Canvas.Left="358" Canvas.Top="84"></Image>
</Canvas>
</ScrollViewer>
then in my c#code you will put this.
private void OnSizeChanged(Object sender, SizeChangedEventArgs args) {
Main.Width = scrl.ViewportWidth;
Main.Height = scrl.ViewportHeight;
}
this will make your canvas zoom to pinch and pan enabled.
the image there is just a sample to see if the zoom function is working. It works fine.
the best way is creating a matrix for your canvas and scale and move the matrix like this :
Canvas can = new Canvas();
Matrix matrix = new Matrix();
matrix.Translate(50, 0);
matrix.Scale(1.5,1.5);
can.RenderTransform = new MatrixTransform(matrix);
Hope helps you
Related
Problem : I am using UWP Community Toolkit Scale animation and it works as expected for most of the images in the GridView, but for some the image goes out of bounds . (Please see the image below)
I have detected that the issue happens when the image width is more than 2x (2 times) the height of the image. That is when the image is very wide.
Code
I am using a user control as data template
Xaml :
<!-- Grid View -->
<GridView x:Name="gridView" SelectionChanged="gridView_SelectionChanged">
<GridView.ItemTemplate>
<DataTemplate>
<local:GridViewMenu/>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<!-- GridViewMenu User Control markup -->
<Grid>
<StackPanel>
<Image Source="{Binding webformatURL}" Stretch="UniformToFill" PointerEntered="image_PointerEntered" PointerExited="image_PointerExited"/>
</StackPanel>
</Grid>
C# Code :
private void image_PointerEntered(object sender, PointerRoutedEventArgs e)
{
Image img = sender as Image;
img.Scale(centerX: (float)(grid.ActualWidth / 2),
centerY: 100,
scaleX: 1.2f,
scaleY: 1.2f,
duration: 500, delay: 0).StartAsync();
}
private void image_PointerExited(object sender, PointerRoutedEventArgs e)
{
Image img = sender as Image;
img.Scale(centerX: (float)(grid.ActualWidth / 2),
centerY: 100,
scaleX: 1f,
scaleY: 1f,
duration: 500, delay: 0).StartAsync();
}
Result (Top left image is not scaling as expected, that is, it is going out of bounds)
How can I solve this issue ?
The scale animation of UWP Community Toolkit package actually use the CompositeTransform class for scaling. According to the description of Transforms and layout section:
Because layout comes first, you'll sometimes get unexpected results if you transform elements that are in a Grid cell or similar layout container that allocates space during layout. The transformed element may appear truncated or obscured because it's trying to draw into an area that didn't calculate the post-transform dimensions when dividing space within its parent container.
So that the parts overflow the bound that being truncated are unexpected. In another words, the image goes out is the transform expected. The current way you are using to meet your requirements is not reliable. If you change width-height ratio of GridViewMenu to 1.0 , you may find more images that width-height ratio larger than 1.0 will go out.
For a solution inside GridView, you could consider to use the ScrollViewer to zoom in the image instead, which can ensure the image is limited in a fixed area. For example:
<Grid x:Name="grid">
<ScrollViewer
x:Name="currentscroll"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden">
<Image
x:Name="myImage"
Width="300"
Height="180"
PointerEntered="image_PointerEntered"
PointerExited="image_PointerExited"
Source="{Binding webformatURL}"
Stretch="UniformToFill">
</Image>
</ScrollViewer>
</Grid>
Code behind:
private void image_PointerEntered(object sender, PointerRoutedEventArgs e)
{
currentscroll.ChangeView(0, 0, 1.2f );
}
private void image_PointerExited(object sender, PointerRoutedEventArgs e)
{
currentscroll.ChangeView(0, 0, 1.0f);
}
You can try to use clipping:
<Grid>
<StackPanel>
<Image Source="{Binding webformatURL}" Stretch="UniformToFill"
PointerEntered="image_PointerEntered"
PointerExited="image_PointerExited">
<Image.Clip>
<RectangleGeometry Rect="0,0,300,150" />
</Image.Clip>
</Image>
</StackPanel>
</Grid>
300 and 150 would be the width and height of the grid item.
Otherwise it looks like a bug in the UWP Community Toolkit, it would be best to report it as an issue on GitHub.
I am performing scaling, translation and rotation on an image using RenderTransform. Transformations are not done using mouse events, rather, the user will click a button and then the image will scale/translate/rotate on a fixed value.
My problem is I want to determine the new position/size of the image each time a scaling, translation or rotation is performed. So I added Changed events on the code-behind. The question is how do you get the new position/size?
Please take a look at what I've done so far:
XAML:
<Border x:Name="mainImageBorderCtrl" ClipToBounds="True">
<Grid x:Name="imageGridCtrl">
<Grid.RenderTransform>
<TranslateTransform Changed="TranslateTransform_Changed"/>
</Grid.RenderTransform>
<Image x:Name="mainImageCtrl" RenderTransformOrigin="0.5, 0.5" Source="{Binding Image}">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform Changed="ScaleTransform_Changed"/>
<RotateTransform />
</TransformGroup>
</Image.RenderTransform>
</Image>
</Grid>
</Border>
Please note that I separated TranslateTransform from ScaleTransform and RotateTransform so that the orientation won't be affected.
The following are the 2 changed events. In here, I expect to get the new position/size every time a scale or a translate has occurred. But the bounds values do not change.
Code-behind
private Rect bounds;
private void TranslateTransform_Changed(object sender, EventArgs e)
{
bounds = imageGridCtrl.TransformToAncestor(mainImageBorderCtrl).TransformBounds(new Rect(imageGridCtrl.RenderSize));
}
private void ScaleTransform_Changed(object sender, EventArgs e)
{
bounds = imageGridCtrl.TransformToAncestor(mainImageBorderCtrl).TransformBounds(new Rect(imageGridCtrl.RenderSize));
}
EDIT: My goal here is to restrict the translation(panning) of the Image inside the Border control, that's why I need to get the bounds of the Image after each transformation, whatever the transformation is, so that I can check whether the bounds of the Image exceeds the Border.
TranslateTransform is generating correct bounds. However, for ScaleTransform you are getting bounds on control imageGridCtrl but the transform is added as child of control mainImageCtrl.
So you should use mainImageCtrl in place of imageGridCtrl to get correct bounds:
bounds = mainImageCtrl.TransformToAncestor(mainImageBorderCtrl)
.TransformBounds(new Rect(mainImageCtrl.RenderSize));
The transform change events fire too early. You should hook up to LayoutUpdated event of the image control. Then try:
mainImageCtrl.TransformToAncestor(mainImageBorderCtrl)
.TransformBounds(new Rect(mainImageCtrl.RenderSize))
I'm trying to crop a circle from one image, and put it on top another image in WPF.
The Circle's center changes according to the mouse movements, and needs to be bounded dynamically.
I tried to position two images on top of each other, and use a third image that I draw in real time as an opacity mask.
Could you please provide short code to solve this problem efficiently ?
The code below describes what you can do with an OpacityMask. It's a little counterintuitive, because we expect a XAML rendering to layer elements bottom-to-top.
However, in this case you want your "background" image to layer on top of the foreground, because the OpacityMask will serve to display only that portion of the foreground described by the position and size of the VisualBrush, rendering the rest transparent. It's given as follows:
<Grid x:Name="MainGrid" MouseMove="Grid_MouseMove">
<Rectangle Fill="Red" ></Rectangle>
<Rectangle Fill="Green">
<Rectangle.OpacityMask>
<VisualBrush Stretch="None" >
<VisualBrush.Visual>
<Ellipse Width="40" Height="40" StrokeThickness="1" Fill="Black" />
</VisualBrush.Visual>
<VisualBrush.RelativeTransform>
<TransformGroup>
<TranslateTransform x:Name="OpacityFilterTransform" X="1" Y="1"/>
</TransformGroup>
</VisualBrush.RelativeTransform>
</VisualBrush>
</Rectangle.OpacityMask>
</Rectangle>
</Grid>
Then, this event handler code computes the position of the ellipse and applies it to the OpacityFilter's TranslateTransform object, giving you control over the position of the image.
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
var position = e.GetPosition(this);
var height = MainGrid.ActualHeight;
var width = MainGrid.ActualWidth;
// with the position values, interpolate a TranslateTransform for the opacity mask
var transX = position.X / width;
var transY = position.Y / height;
OpacityFilterTransform.X = transX - 0.5;
OpacityFilterTransform.Y = transY - 0.5;
}
This solution should work for any descendant of Visual you care to layer.
I'm creating an application for WP7 where the background of it (a grid) receives the image from the camera. Here is how it works:
XAML:
<Grid x:Name="ContentPanel" Grid.Row="1">
<Grid.Background>
<VideoBrush x:Name="video" />
</Grid.Background>
</Grid>
C#:
Microsoft.Devices.PhotoCamera m_camera;
// Constructor
public MainPage()
{
InitializeComponent();
Loaded += (s, e) =>
{
m_camera = new Microsoft.Devices.PhotoCamera();
video.SetSource(m_camera);
};
}
My question: Is there a way to access the zoom options of the camera with this? Or can I define the source as the camera totally zoomed in or out?
Unfortunately, no. You can manipulate a higher resolution image to fill the background with only the part that you'd like to zoom in on. See resolution example here
I have a canvas in my Window and occasionally I want to take snapshots of the content.
I have the following method to do so:
private PngBitmapEncoder captureVisual(Visual v)
{
RenderTargetBitmap bmp = new RenderTargetBitmap(546, 410, 120, 96, PixelFormats.Pbgra32);
bmp.Render(v);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Interlace = PngInterlaceOption.On;
encoder.Frames.Add(BitmapFrame.Create(bmp));
return encoder;
}
I call this method with the following method which is called by a press button action(the idea is to add the picture to another canvas which will display the "photo":
private void take_photo(Object sender, RoutedEventArgs e)
{
System.Windows.Controls.Image photoImg = new System.Windows.Controls.Image();
BitmapEncoder enc = captureVisual(videoCanvas);
photoImg.Source = enc.Frames[0];
photoCanvas.Children.Add(photoImg);
}
The problem:
When I take the photo the origin of the photo/bitmap is not the origin of the canvas but the origin of the window. So the Canvas appears in the bitmap translated towards bottom/right in the same position as it is in relation to the top-left corner of the window. I have no idea why this is happening.
As an example see the pic below: There is white space to the top left and the actual content is translated to the bottom right. The origin should be where the actual image is located. I will try to put around a border to make it easier to visualize the problem(sorry I don't know how to put a border around the image if anyone knows please tell me or edit it yourself. Thanks).
Thanks to the comment of dowhilefor I got the answer from this link:
The problem is WPF implicitly adds the margins of the Canvas in relation to the parent container. In my case the parent container was a Grid and I didn't define any margins, the canvas was filling a specific cell. Nevertheless this somehow gets incorporated in the bitmap.
The solution, wrap the Canvas around another Canvas. That's stupid but it works. IMHO this is a bug in WPF:
<Canvas
Name="outerCanvas"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Grid.Row="1" Grid.Column="1">
<Canvas
Name="videoCanvas"
Canvas.Left="0"
Canvas.Top="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
/>
</Canvas>