UWP - zooming image (with pinch zoom and double tap), with Flip View - c#

I need to show images (using Flip View controll) and allow users to zoom them (with pinch zoom and with double tap) and drag zoomed image.
I would like it to be compatible with Flip View (I mean it shouldn't ovveride draging gesture).
What is the best way to achieve that?

I need to show images (using Flip View controll) and allow users to zoom them (with pinch zoom and with double tap) and drag zoomed image.
We can use the ScrollViewer control and UIElement.DoubleTapped event to allow users to zoom the images (with pinch zoom and with double tap) and drag zoomed image.
In order to zoom the image with pinch zoom and drag zoomed image. We can put the Image into the ScrollViewer.
We can add UIElement.DoubleTapped event in the ScrollViewer to zoom the image with double tap.
For example:
In XAML:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<FlipView Name="MyFlipView">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="local:MyImage">
<ScrollViewer MinZoomFactor="1" DoubleTapped="scrollViewer_DoubleTapped"
ZoomMode="Enabled">
<Image Source="{Binding Path}" />
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
</Grid>
In code behind:
public ObservableCollection<MyImage> MyImages;
public MainPage()
{
this.InitializeComponent();
MyImages = new ObservableCollection<MyImage>();
MyImages.Add(new MyImage("ms-appx:///Assets/cliff.jpg"));
MyImages.Add(new MyImage("ms-appx:///Assets/grapes.jpg"));
MyImages.Add(new MyImage("ms-appx:///Assets/rainer.jpg"));
MyImages.Add(new MyImage("ms-appx:///Assets/sunset.jpg"));
MyImages.Add(new MyImage("ms-appx:///Assets/valley.jpg"));
MyFlipView.ItemsSource = MyImages;
}
private async void scrollViewer_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
{
var scrollViewer = sender as ScrollViewer;
var doubleTapPoint = e.GetPosition(scrollViewer);
if (scrollViewer.ZoomFactor != 1)
{
scrollViewer.ZoomToFactor(1);
}
else if (scrollViewer.ZoomFactor == 1)
{
scrollViewer.ZoomToFactor(2);
var dispatcher = Window.Current.CoreWindow.Dispatcher;
await dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
{
scrollViewer.ScrollToHorizontalOffset(doubleTapPoint.X);
scrollViewer.ScrollToVerticalOffset(doubleTapPoint.Y);
});
}
}
And the MyImage Class code:
public class MyImage
{
public MyImage()
{
}
public MyImage(string uri)
{
this.Path = uri;
}
public string Path { get; set; }
}

Related

Handling trackpad zoom in WinUI 3 Canvas

Many Windows applications support two fingered touchpad gestures. You can scroll by moving two fingers horizontally or vertically, and you can zoom by changing the distance between the fingers.
I'm trying replicate this behaviour in a WinUI 3 canvas.
According to the Microsoft documentation "The touchpad does not raise manipulation events. Instead, pointer events will be raised for touchpad input."
Listening for the PointerWheelChanged event handler, I'm able to detect two finger scrolls with the following code
private void OnPointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
var pointer = e.GetCurrentPoint(myCanvas);
var isHorizontalScroll = pointer.Properties.IsHorizontalMouseWheel;
var scrollDelta = pointer.Properties.MouseWheelDelta;
// Perform the scrolling
e.Handled = true;
}
But, I'm unable to get the fingers position so I can calculate the zoom delta. Is there some lower API I can use that will give me access to the individual fingers? Or some other way to capture the zoom gesture?
I have created a little sample for you, which handles zoom using the trackpad:
MainWindow.xaml:
<Canvas x:Name="myCanvas" PointerWheelChanged="Canvas_PointerWheelChanged" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Green">
<Rectangle Width="40" Height="40" Fill="Red" Canvas.Left="0" Canvas.Top="0" Canvas.ZIndex="0" />
</Canvas>
MainWindow.xaml.cs:
private void Canvas_PointerWheelChanged(object sender, PointerRoutedEventArgs e)
{
var ctrl = Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Control
if (ctrl.HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down))
{
var delta = e.GetCurrentPoint(myCanvas).Properties.MouseWheelDelta;
//Here you can handle your zooming
//My sample just resizes the rectangle
foreach (UIElement children in myCanvas.Children)
{
if (children is Rectangle rect)
{
double newSize = rect.Height + delta;
if (newSize < 0)
newSize = 0;
rect.Height = rect.Width = newSize;
}
}
}
}
The trick is, to check for the control-key press in the PointerWheelChanged-event, because the touchpad behaves the same like when you zoom using control + Mousewheel

UWP: Reveal full, unclipped ImageBrush on tapped event

I have a series of rectangles in which the user can add images to, by dragging the images in.
The image is then scaled down in proportion and the rectangle is then filled with an ImageBrush.
I need for the user to be able to manipulate the image within the rectangle to fit their needs. Like any photo collage app does.
My question is: How can I show the full, unmasked image on top of the rectangle so that the user can manipulate it to their needs? I don't know where to start with this one.
private async void Mask_Drop(object sender, DragEventArgs e)
{
Rectangle maskSq = e.OriginalSource as Rectangle;
var maskW = maskSq.Width.ToSingle();
var maskH = maskSq.Height.ToSingle();
double maskX = Canvas.GetLeft(maskSq);
double maskY = Canvas.GetTop(maskSq);
// Image sizes for bounding to mask
float boundH = Convert.ToSingle(size.Height);
float boundW = Convert.ToSingle(size.Width);
maskSq.Fill = new ImageBrush
{
ImageSource = new BitmapImage(new Uri("ms-appdata:///local/" + SelectedImage.Name, UriKind.Absolute)),
Stretch = Stretch.UniformToFill
};
}
private void Tap_Collage(object sender, TappedRoutedEventArgs e)
{
//Gets the full image from ImageBrush
ImageBrush brush = (ImageBrush)(((Rectangle)sender).Fill);
Rectangle rect = sender as Rectangle;
//Mask sure rectangle does not drag, just the image brush
rect.CanDrag = false;
rect.StrokeThickness = 6;
//Drag Image Functionality
rect.ManipulationDelta += ImageManipulation.Resize_ImageEdit;
ImageManipulation.ImageEdit_Drag = new TranslateTransform();
brush.Transform = ImageManipulation.ImageEdit_Drag;
//Zoom Image Functionality
ImageManipulation.ImageEdit_Zoom = new ScaleTransform();
brush.RelativeTransform = ImageManipulation.ImageEdit_Zoom;
}
Class
public static class ImageManipulation
{
public static TranslateTransform ImageEdit_Drag;
public static ScaleTransform ImageEdit_Zoom;
public static RotateTransform ImageEdit_Rotate;
public static void Resize_ImageEdit(object sender, ManipulationDeltaRoutedEventArgs e)
{
ImageEdit_Drag.X += e.Delta.Translation.X;
ImageEdit_Drag.Y += e.Delta.Translation.Y;
ImageEdit_Zoom.ScaleX *= e.Delta.Scale;
ImageEdit_Zoom.ScaleY *= e.Delta.Scale;
if (ImageEdit_Zoom.ScaleX < 1.0)
{
ImageEdit_Zoom.ScaleX = 1.0;
ImageEdit_Zoom.ScaleY = 1.0;
}
ImageEdit_Rotate.Angle += e.Delta.Rotation;
}
}
XAML
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="local:CollageGrid">
<Rectangle Width="{Binding CollageW}" Height="{Binding CollageH}" AllowDrop="True" CanDrag="True" Fill="Transparent"
Drop="Mask_Drop"
DragOver="Mask_DragOver"
ManipulationMode="TranslateX, TranslateY" Stroke="Black" StrokeThickness="2" DragEnter="Mask_DragEnter" DragLeave="Mask_DragLeave" Tapped="Tap_Collage">
<Rectangle.RenderTransform>
<TranslateTransform X="{Binding CollageX}" Y="{Binding CollageY}"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
Example of what I'm looking to acheive:
How it looks currently:
You can try to replace the Rectangle with the Polyline to draw the Rectangle so that you can access the Image which is on the bottom of the Rectangle.
<Grid>
<Image Source="Assets/image1.jpg" Width="800"
Height="400" Tapped="Image_Tapped" />
<Polyline Stroke="Black" HorizontalAlignment="Center"
VerticalAlignment="Center"
StrokeThickness="4" Tapped="Polyline_Tapped"
Points="0,0,0,200,200,200,200,0,0,0" />
</Grid>
---Update---
UniformToFill will cause the image is resized to fill the destination dimensions while it preserves its native aspect ratio. If the aspect ratio of the destination rectangle differs from the source, the source content is clipped to fit in the destination dimensions. So it is not suitable for your requirement. You should manually scale the Image to make image fit one of your Rectangle's border and keep other part out of the Rectangle.
It seems you put the image as the Rectangle's background image brush, there are no other place to display the image out of the Rectangle. So I think, we may take a considerition for a new scenario.
As my pervious reply, using Image control to display the image and Polylines to draw a Rectangle above the Image so that we can operate the Image which is below the Rectangle using the manipulation events to fit the rectangle, meanwhile we can also use the community toolkit BackdropBlurBrush on the xaml layout to Blur the outer image.

Keeping position and size of canvas after panning canvas in wpf

I'm a newbie, I'm trying to built a visual drawing. I have a canvas and images (images which are created by clicked event) on this canvas.
Code XAML:
<Canvas Name="drawing" Background="Black"
MouseDown="drawing_MouseDown"
MouseMove="drawing_MouseMove"
MouseUp="drawing_MouseUp">
<Canvas.RenderTransform>
<MatrixTransform/>
</Canvas.RenderTransform>
</Canvas>
Code-behind:
namespace Panning_used_MatrixTransform
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private Point firstPoint = new Point();
private void drawing_MouseDown(object sender, MouseButtonEventArgs e)
{
firstPoint = e.GetPosition(this);
drawing.CaptureMouse();
}
private void drawing_MouseMove(object sender, MouseEventArgs e)
{
if (e.MiddleButton == MouseButtonState.Pressed)
{
var lsPoint = e.GetPosition(this);
var res = lsPoint - firstPoint;
var element = sender as UIElement;
var transform = element.RenderTransform as MatrixTransform;
var matrix = transform.Matrix;
matrix.TranslatePrepend(res.X, res.Y);
transform.Matrix = matrix;
//udate first point
firstPoint = lsPoint;
}
}
private void drawing_MouseUp(object sender, MouseButtonEventArgs e)
{
drawing.ReleaseMouseCapture();
}
}
}
I don't know how to keeping position and size of canvas not change when we panning this canvas (like CAD drawing).
More information:
I want to create the software which allow at the beginning we has nothing on the canvas. When we click a button, we can create some images where we want on the canvas. And clearly at the beginning nothing on it (So nothing on the canvas - XAML code).
Please help me!
For your requirement, the manipulation should be performed on the images, instead of the canvas. So when you pan the images, the canvas stays still.
I make a few changes to the XAML code, add another image for testing, also move the event from the canvas to the outer grid, but I do not rename your event handlers so you can compile the new code more easily.
<Grid Background="Black"
MouseDown="drawing_MouseDown"
MouseMove="drawing_MouseMove"
MouseUp="drawing_MouseUp">
<Canvas Name="drawing">
<Image x:Name="imgSource" Source="Resources/my-pic.png"
Height="100" Width="100" Canvas.Top="30" Canvas.Left="30">
<Image.RenderTransform>
<MatrixTransform/>
</Image.RenderTransform>
</Image>
<Image x:Name="imgSource2" Source="Resources/my-pic.png"
Height="100" Width="100" Canvas.Top="230" Canvas.Left="230">
<Image.RenderTransform>
<MatrixTransform/>
</Image.RenderTransform>
</Image>
</Canvas>
</Grid>
In the code behind, I apply the manipulation to each child element of the canvas.
I also remove the CaptureMouse()/ReleaseMouseCapture() since I think those two lines are not necessary.
private void drawing_MouseMove(object sender, MouseEventArgs e)
{
if (e.MiddleButton == MouseButtonState.Pressed))
{
var lsPoint = e.GetPosition(this);
var res = lsPoint - firstPoint;
foreach (UIElement element in drawing.Children)
{
var transform = element.RenderTransform as MatrixTransform;
var matrix = transform.Matrix;
matrix.TranslatePrepend(res.X, res.Y);
transform.Matrix = matrix;
}
//udate first point
firstPoint = lsPoint;
}
}

Image crop with custom rectangle

I'm creating app for image scan with WIA. But my images has a lot of unused space if scanned document size is not big. I need to crop that unused space like in Paint with Cross cursor and rectangle. How to do that in WPF?
Code of image cropping is:
private static Image cropImage(Image img, Rectangle cropArea)
{
Bitmap bmpImage = new Bitmap(img);
Bitmap bmpCrop = bmpImage.Clone(cropArea,bmpImage.PixelFormat);
return (Image)(bmpCrop);
}
Put the Image control in a Canvas and add an invisible rectangle to the canvas as well.
On left mouse button down set the top left of the rectangle at the mouse coordinates and make it visible.
On mouse move (and mouse button still down), set the size of the rectangle, so the opposite of the first corner moves with the mouse.
Example:
<Window x:Class="TestWpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpfApplication"
Title="MainWindow"
Height="350"
Width="525">
<Canvas MouseLeftButtonDown="Canvas_MouseLeftButtonDown"
MouseMove="Canvas_MouseMove"
Background="AntiqueWhite">
<Image Width="500"
Height="300" />
<Rectangle x:Name="selectionRectangle"
StrokeThickness="1"
Stroke="LightBlue"
Fill="#220000FF"
Visibility="Collapsed" />
</Canvas>
</Window>
And the code behind:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace TestWpfApplication
{
public partial class MainWindow : System.Windows.Window
{
public MainWindow()
{
InitializeComponent();
}
private void Canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var mousePosition = e.GetPosition(sender as UIElement);
Canvas.SetLeft(selectionRectangle, mousePosition.X);
Canvas.SetTop(selectionRectangle, mousePosition.Y);
selectionRectangle.Visibility = System.Windows.Visibility.Visible;
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
var mousePosition = e.GetPosition(sender as UIElement);
selectionRectangle.Width = mousePosition.X - Canvas.GetLeft(selectionRectangle);
selectionRectangle.Height = mousePosition.Y - Canvas.GetTop(selectionRectangle);
}
}
}
}
Note that the Canvas will only respond to a click when it has a color.
Also: you'll need to handle the situation where the user drags the selection right to left and/or bottom to top because that will introduce negative sizes for the width and height of the rectangle. But I'll leave that to you.
Thanks for this code. My actual issues realted to CROP iamge show in another iamge control. How ist possible in WPF. Please fallow this link.
http://social.msdn.microsoft.com/Forums/en-US/302bb1c8-e272-48b1-982d-12cf2aefe8a3/how-to-crop-image-show-in-another-image-control-?forum=wpf
Thank

WPF - how do I bind a control's position to the current mouse position?

Is there a way to bind to the mouse position in WPF in the XAML file? Or does that have to be done in code? I have a control inside a Canvas, and I just want the control to follow the mouse while the mouse cursor is inside the Canvas.
Thanks
EDIT:
OK, I figured it out a relatively easy way using the code-behind file. I added a MouseMove event handler on the Canvas, and then added:
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
// Get the x and y coordinates of the mouse pointer.
System.Windows.Point position = e.GetPosition(this);
double pX = position.X;
double pY = position.Y;
// Sets the position of the image to the mouse coordinates.
myMouseImage.SetValue(Canvas.LeftProperty, pX);
myMouseImage.SetValue(Canvas.TopProperty, pY);
}
using http://msdn.microsoft.com/en-us/library/ms746626.aspx as a guideline.
I tried to make a kind of decorator for this purpose.
You wrap the object, mouse position above which you want to control and bind some control to decorator MousePosition property.
public class MouseTrackerDecorator : Decorator
{
static readonly DependencyProperty MousePositionProperty;
static MouseTrackerDecorator()
{
MousePositionProperty = DependencyProperty.Register("MousePosition", typeof(Point), typeof(MouseTrackerDecorator));
}
public override UIElement Child
{
get
{
return base.Child;
}
set
{
if (base.Child != null)
base.Child.MouseMove -= _controlledObject_MouseMove;
base.Child = value;
base.Child.MouseMove += _controlledObject_MouseMove;
}
}
public Point MousePosition
{
get
{
return (Point)GetValue(MouseTrackerDecorator.MousePositionProperty);
}
set
{
SetValue(MouseTrackerDecorator.MousePositionProperty, value);
}
}
void _controlledObject_MouseMove(object sender, MouseEventArgs e)
{
Point p = e.GetPosition(base.Child);
// Here you can add some validation logic
MousePosition = p;
}
}
and XAML
<local:MouseTrackerDecorator x:Name="mouseTracker">
<Canvas Width="200" Height="200" Background="Red">
<Button Width="20" Height="20" Canvas.Left="{Binding ElementName=mouseTracker, Path=MousePosition.X}" Canvas.Top="{Binding ElementName=mouseTracker, Path=MousePosition.Y}" />
</Canvas>
</local:MouseTrackerDecorator>
Tried a few examples only. The msdn documentation is , i think, incorrectly worded
"How to: Make an Object Follow the Mouse Pointer"
should be
"How to: Make an object's size increase based on mouse position"
anyway.
I was able to achieve this effect by changing the canvas properties. Also not sure why everyone was attaching the event handler to the objects next top level layout property and not the window. Maybe you and most of the examples online are going for a different effect
<Window x:Class="FollowMouse.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" MouseMove="MouseMoveHandler">
<Canvas>
<Ellipse Name="ellipse" Fill="LightBlue"Width="100" Height="100"/>
</Canvas>
code behind
private void MouseMoveHandler(object sender, MouseEventArgs e)
{
/// Get the x and y coordinates of the mouse pointer.
System.Windows.Point position = e.GetPosition(this);
double pX = position.X;
double pY = position.Y;
/// Sets eclipse to the mouse coordinates.
Canvas.SetLeft(ellipse, pX);
Canvas.SetTop(ellipse, pY);
Canvas.SetRight(ellipse, pX);
}

Categories

Resources